From e166f13fbe81a70190b39aead9ec085f2589422b Mon Sep 17 00:00:00 2001 From: "Dina Berry (She/her)" Date: Sat, 4 Apr 2026 11:42:04 -0700 Subject: [PATCH] ci: add diff guard and git staging safety docs - Add Diff Size Guard job to squad-repo-health.yml that warns when single-commit PRs touch 30+ files (likely contamination) - Add branch contamination prevention rules to copilot-instructions.md - Add diff verification checklist item to PR template Prevents the recurring issue where git add -A on stale branches contaminates PRs with unrelated changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/contamination-guard.md | 8 +++++++ .github/PULL_REQUEST_TEMPLATE.md | 4 ++++ .github/copilot-instructions.md | 30 +++++++++++++++++++++++++ .github/workflows/squad-repo-health.yml | 27 ++++++++++++++++++++++ CONTRIBUTING.md | 1 + 5 files changed, 70 insertions(+) create mode 100644 .changeset/contamination-guard.md diff --git a/.changeset/contamination-guard.md b/.changeset/contamination-guard.md new file mode 100644 index 000000000..29ed0ae95 --- /dev/null +++ b/.changeset/contamination-guard.md @@ -0,0 +1,8 @@ +--- +--- + +ci: add PR contamination prevention guard + +New diff-guard CI check warns when single-commit PRs touch 30+ files +(likely branch contamination). Updated copilot-instructions with branch +creation and staging safety rules. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fdf052135..cebbf6ba2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,6 +9,9 @@ --- +### ⚠️ Quick Check +- [ ] If SDK/CLI source files changed: completed the applicable Changeset step below (`npx changeset add` / `.changeset/*.md`, direct `CHANGELOG.md` entry for maintainers, or `skip-changelog` label for no user-facing changes) + ### PR Readiness Checklist > The PR readiness bot will validate these automatically after push. @@ -17,6 +20,7 @@ #### Branch & Commit - [ ] Branch created from `dev` (not `main`) - [ ] Branch is up to date with `dev` (`git fetch upstream && git rebase upstream/dev`) +- [ ] Verified diff contains only intended changes (`git diff --cached --stat`) - [ ] PR is **not** in draft mode (mark ready when checks pass) - [ ] Commit history is clean (squash fixups before review) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 64f680876..5271c6c53 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,6 +25,13 @@ Before pushing any commit, verify: 3. `npm run build` — build succeeds with your changes 4. Commit message references the issue: `Closes #N` +### Branch Contamination Prevention +- When creating PR branches, always branch from the latest upstream dev: `git fetch upstream && git checkout dev && git rebase upstream/dev && git checkout -b ` +- NEVER use `git reset --soft` to squash on branches behind dev — it picks up all the delta between your branch and dev, contaminating the commit +- To squash safely: use `git rebase -i` or amend the single commit with `git commit --amend` +- Before committing, ALWAYS verify the diff: `git diff --cached --stat` should show ONLY your intended files +- If you see unexpected files in the diff, unstage them: `git reset HEAD ` + ### Red Flags — STOP and Ask If you see any of these, STOP immediately and comment on the issue asking for guidance: - More than 20 files in your diff @@ -116,6 +123,29 @@ When reviewing or creating PRs, consult these skills for domain-specific checkli Read the relevant skill(s) before submitting or reviewing a PR that touches their domain. +## PR Scope Rules + +Scope is determined by PR labels, not by branch name. Continue using the squad branch convention: `squad/{issue-number}-{slug}`. +- **Repo-health PRs** (label `repo-health`): Only modify `.github/`, `scripts/`, root config files, test files, and docs. NEVER modify files under `packages/*/src/`. +- **Product PRs** (label `fix` or `feat`): May modify product source code. Must include changesets when required by the rules below. +- If a task requires both infrastructure and product changes, create separate PRs. + +## Changeset Requirement + +Any PR that modifies files under `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a changeset file. + +- Run `npx changeset add` and select the affected package(s) +- Or manually create `.changeset/{descriptive-name}.md` with format: + ``` + --- + '@bradygaster/squad-cli': patch + --- + Brief description of the change + ``` +- Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes +- The `changelog-gate` CI check will fail without this +- Escape hatch: add the `skip-changelog` label (use sparingly) + ## Decisions If you make a decision that affects other team members, write it to: diff --git a/.github/workflows/squad-repo-health.yml b/.github/workflows/squad-repo-health.yml index d6fe1fea1..a559fc353 100644 --- a/.github/workflows/squad-repo-health.yml +++ b/.github/workflows/squad-repo-health.yml @@ -47,6 +47,33 @@ jobs: echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT exit $EXIT_CODE + # ─── Diff Size Guard (WARNING) ───────────────────────────────────── + diff-guard: + name: Diff Size Guard + runs-on: ubuntu-latest + timeout-minutes: 2 + if: github.actor != 'dependabot[bot]' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Fetch PR head (data only — not executed) + run: git fetch origin ${{ github.event.pull_request.head.sha }} + - name: Check for likely contamination + run: | + git fetch origin dev --quiet + FILE_COUNT=$(git diff --name-only origin/dev...${{ github.event.pull_request.head.sha }} | wc -l) + COMMIT_COUNT=$(git rev-list --count origin/dev..${{ github.event.pull_request.head.sha }}) + + # Heuristic: if a single-commit PR touches more than 30 files, it's likely contaminated + if [ "$COMMIT_COUNT" -le 2 ] && [ "$FILE_COUNT" -gt 30 ]; then + echo "::warning::⚠️ This PR has $COMMIT_COUNT commit(s) but touches $FILE_COUNT files." + echo "::warning::This may indicate branch contamination from broad staging (--all or .) on a stale branch." + echo "::warning::Please verify all changed files are intentional: git diff --name-only origin/dev...${{ github.event.pull_request.head.sha }}" + else + echo "✅ Diff looks proportional: $COMMIT_COUNT commit(s), $FILE_COUNT file(s)." + fi + # ─── Squad File Leakage (WARNING) ─────────────────────────────────── squad-leakage: name: Squad File Leakage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f8f792b54..d44fe49fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -300,6 +300,7 @@ GitHub Actions runs on every push: 2. **Test:** `npm test` 3. **Lint:** `npm run lint` 4. **Changeset status:** `npm run changeset:check` (ensures PRs include a changeset) +5. **Diff Size Guard:** Warns when a single-commit PR touches 30+ files (likely branch contamination from staging all files at once on a stale branch). Always use explicit `git add ` instead. All checks must pass before merge.