From 255ddd8f64a3d595734d4e54b438afc6f819fd6c Mon Sep 17 00:00:00 2001 From: Dina Berry Date: Wed, 8 Apr 2026 08:56:29 -0700 Subject: [PATCH] =?UTF-8?q?feat(ci):=20CI=20template=20overhaul=20?= =?UTF-8?q?=E2=80=94=20performance=20&=20safety=20for=20shipped=20workflow?= =?UTF-8?q?s=20[DO=20NOT=20MERGE]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/ci-template-overhaul.md | 6 ++++ .squad-templates/workflows/squad-ci.yml | 31 +++++++++++++++++-- .squad-templates/workflows/squad-docs.yml | 14 ++++++--- .../workflows/squad-heartbeat.yml | 23 ++++++++++---- .../workflows/squad-insider-release.yml | 10 ++++-- .../workflows/squad-issue-assign.yml | 24 +++++++++++--- .../workflows/squad-label-enforce.yml | 12 +++++-- .squad-templates/workflows/squad-preview.yml | 10 ++++-- .squad-templates/workflows/squad-promote.yml | 10 ++++-- .squad-templates/workflows/squad-release.yml | 10 ++++-- .squad-templates/workflows/squad-triage.yml | 13 ++++++-- .../workflows/sync-squad-labels.yml | 9 ++++-- .../templates/workflows/squad-ci.yml | 31 +++++++++++++++++-- .../templates/workflows/squad-docs.yml | 14 ++++++--- .../templates/workflows/squad-heartbeat.yml | 23 ++++++++++---- .../workflows/squad-insider-release.yml | 10 ++++-- .../workflows/squad-issue-assign.yml | 24 +++++++++++--- .../workflows/squad-label-enforce.yml | 12 +++++-- .../templates/workflows/squad-preview.yml | 10 ++++-- .../templates/workflows/squad-promote.yml | 10 ++++-- .../templates/workflows/squad-release.yml | 10 ++++-- .../templates/workflows/squad-triage.yml | 13 ++++++-- .../templates/workflows/sync-squad-labels.yml | 9 ++++-- .../templates/workflows/squad-ci.yml | 31 +++++++++++++++++-- .../templates/workflows/squad-docs.yml | 14 ++++++--- .../templates/workflows/squad-heartbeat.yml | 23 ++++++++++---- .../workflows/squad-insider-release.yml | 10 ++++-- .../workflows/squad-issue-assign.yml | 24 +++++++++++--- .../workflows/squad-label-enforce.yml | 12 +++++-- .../templates/workflows/squad-preview.yml | 10 ++++-- .../templates/workflows/squad-promote.yml | 10 ++++-- .../templates/workflows/squad-release.yml | 10 ++++-- .../templates/workflows/squad-triage.yml | 13 ++++++-- .../templates/workflows/sync-squad-labels.yml | 9 ++++-- templates/workflows/squad-ci.yml | 31 +++++++++++++++++-- templates/workflows/squad-docs.yml | 14 ++++++--- templates/workflows/squad-heartbeat.yml | 23 ++++++++++---- templates/workflows/squad-insider-release.yml | 10 ++++-- templates/workflows/squad-issue-assign.yml | 24 +++++++++++--- templates/workflows/squad-label-enforce.yml | 12 +++++-- templates/workflows/squad-preview.yml | 10 ++++-- templates/workflows/squad-promote.yml | 10 ++++-- templates/workflows/squad-release.yml | 10 ++++-- templates/workflows/squad-triage.yml | 13 ++++++-- templates/workflows/sync-squad-labels.yml | 9 ++++-- 45 files changed, 538 insertions(+), 132 deletions(-) create mode 100644 .changeset/ci-template-overhaul.md diff --git a/.changeset/ci-template-overhaul.md b/.changeset/ci-template-overhaul.md new file mode 100644 index 000000000..067e0cb20 --- /dev/null +++ b/.changeset/ci-template-overhaul.md @@ -0,0 +1,6 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +CI template overhaul: add caching, timeouts, concurrency groups, SHA-pinned actions, token documentation, and security hardening across all 11 shipped workflow templates. Closes #886. diff --git a/.squad-templates/workflows/squad-ci.yml b/.squad-templates/workflows/squad-ci.yml index 493dafc7c..23eadef50 100644 --- a/.squad-templates/workflows/squad-ci.yml +++ b/.squad-templates/workflows/squad-ci.yml @@ -6,19 +6,46 @@ on: types: [opened, synchronize, reopened] push: branches: [dev, insider] + workflow_dispatch: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + src: ${{ steps.filter.outputs.src }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + id: filter + with: + filters: | + src: + - '!docs/**' + - '!*.md' + - '!.squad/*.md' + - '!.squad/decisions/**' + test: + needs: changes + if: needs.changes.outputs.src == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/.squad-templates/workflows/squad-docs.yml b/.squad-templates/workflows/squad-docs.yml index d801a5635..824553489 100644 --- a/.squad-templates/workflows/squad-docs.yml +++ b/.squad-templates/workflows/squad-docs.yml @@ -11,7 +11,6 @@ on: permissions: contents: read pages: write - id-token: write concurrency: group: pages @@ -20,10 +19,11 @@ concurrency: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '22' cache: npm @@ -38,17 +38,21 @@ jobs: run: npm run build - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs/dist deploy: needs: build runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.squad-templates/workflows/squad-heartbeat.yml b/.squad-templates/workflows/squad-heartbeat.yml index 1b75fda3e..3e363b5f9 100644 --- a/.squad-templates/workflows/squad-heartbeat.yml +++ b/.squad-templates/workflows/squad-heartbeat.yml @@ -21,11 +21,18 @@ permissions: contents: read pull-requests: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: heartbeat: runs-on: ubuntu-latest + timeout-minutes: 15 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check triage script id: check-script @@ -48,7 +55,7 @@ jobs: - name: Ralph — Apply triage decisions if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -97,12 +104,16 @@ jobs: core.info(`🔄 Ralph triaged ${results.length} issue(s)`); - # Copilot auto-assign step (uses PAT if available) + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 + if: success() && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | const fs = require('fs'); diff --git a/.squad-templates/workflows/squad-insider-release.yml b/.squad-templates/workflows/squad-insider-release.yml index 36a1121bf..50d34fcdf 100644 --- a/.squad-templates/workflows/squad-insider-release.yml +++ b/.squad-templates/workflows/squad-insider-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/.squad-templates/workflows/squad-issue-assign.yml b/.squad-templates/workflows/squad-issue-assign.yml index ad140f42d..96f1c4d41 100644 --- a/.squad-templates/workflows/squad-issue-assign.yml +++ b/.squad-templates/workflows/squad-issue-assign.yml @@ -8,16 +8,25 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) - if: startsWith(github.event.label.name, 'squad:') + if: startsWith(github.event.label.name, 'squad:') && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Identify assigned member and trigger work - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -113,10 +122,15 @@ jobs: core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. # Separate step: assign @copilot using PAT (required for coding agent) - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 + if: github.event.label.name == 'squad:copilot' && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | diff --git a/.squad-templates/workflows/squad-label-enforce.yml b/.squad-templates/workflows/squad-label-enforce.yml index 633d220df..90c164fb3 100644 --- a/.squad-templates/workflows/squad-label-enforce.yml +++ b/.squad-templates/workflows/squad-label-enforce.yml @@ -8,14 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: enforce: + if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Enforce mutual exclusivity - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const issue = context.payload.issue; diff --git a/.squad-templates/workflows/squad-preview.yml b/.squad-templates/workflows/squad-preview.yml index 9df39e079..c8b1c8d5b 100644 --- a/.squad-templates/workflows/squad-preview.yml +++ b/.squad-templates/workflows/squad-preview.yml @@ -7,15 +7,21 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Validate version consistency run: | diff --git a/.squad-templates/workflows/squad-promote.yml b/.squad-templates/workflows/squad-promote.yml index 9d315b1d1..cc27fc0a5 100644 --- a/.squad-templates/workflows/squad-promote.yml +++ b/.squad-templates/workflows/squad-promote.yml @@ -13,12 +13,17 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: dev-to-preview: name: Promote dev → preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -69,8 +74,9 @@ jobs: name: Promote preview → main (release) needs: dev-to-preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.squad-templates/workflows/squad-release.yml b/.squad-templates/workflows/squad-release.yml index 6ae0f07fd..b663ac493 100644 --- a/.squad-templates/workflows/squad-release.yml +++ b/.squad-templates/workflows/squad-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/.squad-templates/workflows/squad-triage.yml b/.squad-templates/workflows/squad-triage.yml index d118a2813..fbb097168 100644 --- a/.squad-templates/workflows/squad-triage.yml +++ b/.squad-templates/workflows/squad-triage.yml @@ -8,15 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: triage: - if: github.event.label.name == 'squad' + if: github.event.label.name == 'squad' && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Triage issue via Lead agent - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/.squad-templates/workflows/sync-squad-labels.yml b/.squad-templates/workflows/sync-squad-labels.yml index 699fc680f..0ed46b280 100644 --- a/.squad-templates/workflows/sync-squad-labels.yml +++ b/.squad-templates/workflows/sync-squad-labels.yml @@ -11,14 +11,19 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: sync-labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Parse roster and sync labels - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/packages/squad-cli/templates/workflows/squad-ci.yml b/packages/squad-cli/templates/workflows/squad-ci.yml index 493dafc7c..23eadef50 100644 --- a/packages/squad-cli/templates/workflows/squad-ci.yml +++ b/packages/squad-cli/templates/workflows/squad-ci.yml @@ -6,19 +6,46 @@ on: types: [opened, synchronize, reopened] push: branches: [dev, insider] + workflow_dispatch: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + src: ${{ steps.filter.outputs.src }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + id: filter + with: + filters: | + src: + - '!docs/**' + - '!*.md' + - '!.squad/*.md' + - '!.squad/decisions/**' + test: + needs: changes + if: needs.changes.outputs.src == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-cli/templates/workflows/squad-docs.yml b/packages/squad-cli/templates/workflows/squad-docs.yml index d801a5635..824553489 100644 --- a/packages/squad-cli/templates/workflows/squad-docs.yml +++ b/packages/squad-cli/templates/workflows/squad-docs.yml @@ -11,7 +11,6 @@ on: permissions: contents: read pages: write - id-token: write concurrency: group: pages @@ -20,10 +19,11 @@ concurrency: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '22' cache: npm @@ -38,17 +38,21 @@ jobs: run: npm run build - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs/dist deploy: needs: build runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/packages/squad-cli/templates/workflows/squad-heartbeat.yml b/packages/squad-cli/templates/workflows/squad-heartbeat.yml index 1b75fda3e..3e363b5f9 100644 --- a/packages/squad-cli/templates/workflows/squad-heartbeat.yml +++ b/packages/squad-cli/templates/workflows/squad-heartbeat.yml @@ -21,11 +21,18 @@ permissions: contents: read pull-requests: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: heartbeat: runs-on: ubuntu-latest + timeout-minutes: 15 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check triage script id: check-script @@ -48,7 +55,7 @@ jobs: - name: Ralph — Apply triage decisions if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -97,12 +104,16 @@ jobs: core.info(`🔄 Ralph triaged ${results.length} issue(s)`); - # Copilot auto-assign step (uses PAT if available) + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 + if: success() && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | const fs = require('fs'); diff --git a/packages/squad-cli/templates/workflows/squad-insider-release.yml b/packages/squad-cli/templates/workflows/squad-insider-release.yml index 36a1121bf..50d34fcdf 100644 --- a/packages/squad-cli/templates/workflows/squad-insider-release.yml +++ b/packages/squad-cli/templates/workflows/squad-insider-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-cli/templates/workflows/squad-issue-assign.yml b/packages/squad-cli/templates/workflows/squad-issue-assign.yml index ad140f42d..96f1c4d41 100644 --- a/packages/squad-cli/templates/workflows/squad-issue-assign.yml +++ b/packages/squad-cli/templates/workflows/squad-issue-assign.yml @@ -8,16 +8,25 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) - if: startsWith(github.event.label.name, 'squad:') + if: startsWith(github.event.label.name, 'squad:') && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Identify assigned member and trigger work - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -113,10 +122,15 @@ jobs: core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. # Separate step: assign @copilot using PAT (required for coding agent) - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 + if: github.event.label.name == 'squad:copilot' && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | diff --git a/packages/squad-cli/templates/workflows/squad-label-enforce.yml b/packages/squad-cli/templates/workflows/squad-label-enforce.yml index 633d220df..90c164fb3 100644 --- a/packages/squad-cli/templates/workflows/squad-label-enforce.yml +++ b/packages/squad-cli/templates/workflows/squad-label-enforce.yml @@ -8,14 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: enforce: + if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Enforce mutual exclusivity - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const issue = context.payload.issue; diff --git a/packages/squad-cli/templates/workflows/squad-preview.yml b/packages/squad-cli/templates/workflows/squad-preview.yml index 9df39e079..c8b1c8d5b 100644 --- a/packages/squad-cli/templates/workflows/squad-preview.yml +++ b/packages/squad-cli/templates/workflows/squad-preview.yml @@ -7,15 +7,21 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Validate version consistency run: | diff --git a/packages/squad-cli/templates/workflows/squad-promote.yml b/packages/squad-cli/templates/workflows/squad-promote.yml index 9d315b1d1..cc27fc0a5 100644 --- a/packages/squad-cli/templates/workflows/squad-promote.yml +++ b/packages/squad-cli/templates/workflows/squad-promote.yml @@ -13,12 +13,17 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: dev-to-preview: name: Promote dev → preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -69,8 +74,9 @@ jobs: name: Promote preview → main (release) needs: dev-to-preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/squad-cli/templates/workflows/squad-release.yml b/packages/squad-cli/templates/workflows/squad-release.yml index 6ae0f07fd..b663ac493 100644 --- a/packages/squad-cli/templates/workflows/squad-release.yml +++ b/packages/squad-cli/templates/workflows/squad-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-cli/templates/workflows/squad-triage.yml b/packages/squad-cli/templates/workflows/squad-triage.yml index d118a2813..fbb097168 100644 --- a/packages/squad-cli/templates/workflows/squad-triage.yml +++ b/packages/squad-cli/templates/workflows/squad-triage.yml @@ -8,15 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: triage: - if: github.event.label.name == 'squad' + if: github.event.label.name == 'squad' && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Triage issue via Lead agent - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/packages/squad-cli/templates/workflows/sync-squad-labels.yml b/packages/squad-cli/templates/workflows/sync-squad-labels.yml index 699fc680f..0ed46b280 100644 --- a/packages/squad-cli/templates/workflows/sync-squad-labels.yml +++ b/packages/squad-cli/templates/workflows/sync-squad-labels.yml @@ -11,14 +11,19 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: sync-labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Parse roster and sync labels - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/packages/squad-sdk/templates/workflows/squad-ci.yml b/packages/squad-sdk/templates/workflows/squad-ci.yml index 493dafc7c..23eadef50 100644 --- a/packages/squad-sdk/templates/workflows/squad-ci.yml +++ b/packages/squad-sdk/templates/workflows/squad-ci.yml @@ -6,19 +6,46 @@ on: types: [opened, synchronize, reopened] push: branches: [dev, insider] + workflow_dispatch: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + src: ${{ steps.filter.outputs.src }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + id: filter + with: + filters: | + src: + - '!docs/**' + - '!*.md' + - '!.squad/*.md' + - '!.squad/decisions/**' + test: + needs: changes + if: needs.changes.outputs.src == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-sdk/templates/workflows/squad-docs.yml b/packages/squad-sdk/templates/workflows/squad-docs.yml index d801a5635..824553489 100644 --- a/packages/squad-sdk/templates/workflows/squad-docs.yml +++ b/packages/squad-sdk/templates/workflows/squad-docs.yml @@ -11,7 +11,6 @@ on: permissions: contents: read pages: write - id-token: write concurrency: group: pages @@ -20,10 +19,11 @@ concurrency: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '22' cache: npm @@ -38,17 +38,21 @@ jobs: run: npm run build - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs/dist deploy: needs: build runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/packages/squad-sdk/templates/workflows/squad-heartbeat.yml b/packages/squad-sdk/templates/workflows/squad-heartbeat.yml index 1b75fda3e..3e363b5f9 100644 --- a/packages/squad-sdk/templates/workflows/squad-heartbeat.yml +++ b/packages/squad-sdk/templates/workflows/squad-heartbeat.yml @@ -21,11 +21,18 @@ permissions: contents: read pull-requests: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: heartbeat: runs-on: ubuntu-latest + timeout-minutes: 15 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check triage script id: check-script @@ -48,7 +55,7 @@ jobs: - name: Ralph — Apply triage decisions if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -97,12 +104,16 @@ jobs: core.info(`🔄 Ralph triaged ${results.length} issue(s)`); - # Copilot auto-assign step (uses PAT if available) + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 + if: success() && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | const fs = require('fs'); diff --git a/packages/squad-sdk/templates/workflows/squad-insider-release.yml b/packages/squad-sdk/templates/workflows/squad-insider-release.yml index 36a1121bf..50d34fcdf 100644 --- a/packages/squad-sdk/templates/workflows/squad-insider-release.yml +++ b/packages/squad-sdk/templates/workflows/squad-insider-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-sdk/templates/workflows/squad-issue-assign.yml b/packages/squad-sdk/templates/workflows/squad-issue-assign.yml index ad140f42d..96f1c4d41 100644 --- a/packages/squad-sdk/templates/workflows/squad-issue-assign.yml +++ b/packages/squad-sdk/templates/workflows/squad-issue-assign.yml @@ -8,16 +8,25 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) - if: startsWith(github.event.label.name, 'squad:') + if: startsWith(github.event.label.name, 'squad:') && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Identify assigned member and trigger work - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -113,10 +122,15 @@ jobs: core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. # Separate step: assign @copilot using PAT (required for coding agent) - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 + if: github.event.label.name == 'squad:copilot' && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | diff --git a/packages/squad-sdk/templates/workflows/squad-label-enforce.yml b/packages/squad-sdk/templates/workflows/squad-label-enforce.yml index 633d220df..90c164fb3 100644 --- a/packages/squad-sdk/templates/workflows/squad-label-enforce.yml +++ b/packages/squad-sdk/templates/workflows/squad-label-enforce.yml @@ -8,14 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: enforce: + if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Enforce mutual exclusivity - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const issue = context.payload.issue; diff --git a/packages/squad-sdk/templates/workflows/squad-preview.yml b/packages/squad-sdk/templates/workflows/squad-preview.yml index 9df39e079..c8b1c8d5b 100644 --- a/packages/squad-sdk/templates/workflows/squad-preview.yml +++ b/packages/squad-sdk/templates/workflows/squad-preview.yml @@ -7,15 +7,21 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Validate version consistency run: | diff --git a/packages/squad-sdk/templates/workflows/squad-promote.yml b/packages/squad-sdk/templates/workflows/squad-promote.yml index 9d315b1d1..cc27fc0a5 100644 --- a/packages/squad-sdk/templates/workflows/squad-promote.yml +++ b/packages/squad-sdk/templates/workflows/squad-promote.yml @@ -13,12 +13,17 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: dev-to-preview: name: Promote dev → preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -69,8 +74,9 @@ jobs: name: Promote preview → main (release) needs: dev-to-preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/squad-sdk/templates/workflows/squad-release.yml b/packages/squad-sdk/templates/workflows/squad-release.yml index 6ae0f07fd..b663ac493 100644 --- a/packages/squad-sdk/templates/workflows/squad-release.yml +++ b/packages/squad-sdk/templates/workflows/squad-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/packages/squad-sdk/templates/workflows/squad-triage.yml b/packages/squad-sdk/templates/workflows/squad-triage.yml index d118a2813..fbb097168 100644 --- a/packages/squad-sdk/templates/workflows/squad-triage.yml +++ b/packages/squad-sdk/templates/workflows/squad-triage.yml @@ -8,15 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: triage: - if: github.event.label.name == 'squad' + if: github.event.label.name == 'squad' && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Triage issue via Lead agent - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/packages/squad-sdk/templates/workflows/sync-squad-labels.yml b/packages/squad-sdk/templates/workflows/sync-squad-labels.yml index 699fc680f..0ed46b280 100644 --- a/packages/squad-sdk/templates/workflows/sync-squad-labels.yml +++ b/packages/squad-sdk/templates/workflows/sync-squad-labels.yml @@ -11,14 +11,19 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: sync-labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Parse roster and sync labels - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/templates/workflows/squad-ci.yml b/templates/workflows/squad-ci.yml index 493dafc7c..23eadef50 100644 --- a/templates/workflows/squad-ci.yml +++ b/templates/workflows/squad-ci.yml @@ -6,19 +6,46 @@ on: types: [opened, synchronize, reopened] push: branches: [dev, insider] + workflow_dispatch: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + changes: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + src: ${{ steps.filter.outputs.src }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + id: filter + with: + filters: | + src: + - '!docs/**' + - '!*.md' + - '!.squad/*.md' + - '!.squad/decisions/**' + test: + needs: changes + if: needs.changes.outputs.src == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/templates/workflows/squad-docs.yml b/templates/workflows/squad-docs.yml index d801a5635..824553489 100644 --- a/templates/workflows/squad-docs.yml +++ b/templates/workflows/squad-docs.yml @@ -11,7 +11,6 @@ on: permissions: contents: read pages: write - id-token: write concurrency: group: pages @@ -20,10 +19,11 @@ concurrency: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '22' cache: npm @@ -38,17 +38,21 @@ jobs: run: npm run build - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: path: docs/dist deploy: needs: build runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/templates/workflows/squad-heartbeat.yml b/templates/workflows/squad-heartbeat.yml index 1b75fda3e..3e363b5f9 100644 --- a/templates/workflows/squad-heartbeat.yml +++ b/templates/workflows/squad-heartbeat.yml @@ -21,11 +21,18 @@ permissions: contents: read pull-requests: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: heartbeat: runs-on: ubuntu-latest + timeout-minutes: 15 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check triage script id: check-script @@ -48,7 +55,7 @@ jobs: - name: Ralph — Apply triage decisions if: steps.check-script.outputs.has_script == 'true' && hashFiles('triage-results.json') != '' - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -97,12 +104,16 @@ jobs: core.info(`🔄 Ralph triaged ${results.length} issue(s)`); - # Copilot auto-assign step (uses PAT if available) + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. - name: Ralph — Assign @copilot issues - if: success() - uses: actions/github-script@v7 + if: success() && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: - github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | const fs = require('fs'); diff --git a/templates/workflows/squad-insider-release.yml b/templates/workflows/squad-insider-release.yml index 36a1121bf..50d34fcdf 100644 --- a/templates/workflows/squad-insider-release.yml +++ b/templates/workflows/squad-insider-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/templates/workflows/squad-issue-assign.yml b/templates/workflows/squad-issue-assign.yml index ad140f42d..96f1c4d41 100644 --- a/templates/workflows/squad-issue-assign.yml +++ b/templates/workflows/squad-issue-assign.yml @@ -8,16 +8,25 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) - if: startsWith(github.event.label.name, 'squad:') + if: startsWith(github.event.label.name, 'squad:') && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 + env: + HAS_COPILOT_TOKEN: ${{ secrets.COPILOT_ASSIGN_TOKEN != '' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Identify assigned member and trigger work - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); @@ -113,10 +122,15 @@ jobs: core.info(`Issue #${issue.number} assigned to ${assignedMember.name} (${assignedMember.role})`); + # COPILOT_ASSIGN_TOKEN: A fine-grained GitHub PAT with 'Issues: Write' permission. + # Used to assign the @copilot coding agent to issues. Optional — if not set, + # @copilot assignment is skipped. + # Create at: Settings > Developer settings > Fine-grained tokens + # Required permission: Issues (Read and Write) on this repository only. # Separate step: assign @copilot using PAT (required for coding agent) - name: Assign @copilot coding agent - if: github.event.label.name == 'squad:copilot' - uses: actions/github-script@v7 + if: github.event.label.name == 'squad:copilot' && env.HAS_COPILOT_TOKEN == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.COPILOT_ASSIGN_TOKEN }} script: | diff --git a/templates/workflows/squad-label-enforce.yml b/templates/workflows/squad-label-enforce.yml index 633d220df..90c164fb3 100644 --- a/templates/workflows/squad-label-enforce.yml +++ b/templates/workflows/squad-label-enforce.yml @@ -8,14 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: enforce: + if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Enforce mutual exclusivity - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const issue = context.payload.issue; diff --git a/templates/workflows/squad-preview.yml b/templates/workflows/squad-preview.yml index 9df39e079..c8b1c8d5b 100644 --- a/templates/workflows/squad-preview.yml +++ b/templates/workflows/squad-preview.yml @@ -7,15 +7,21 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Validate version consistency run: | diff --git a/templates/workflows/squad-promote.yml b/templates/workflows/squad-promote.yml index 9d315b1d1..cc27fc0a5 100644 --- a/templates/workflows/squad-promote.yml +++ b/templates/workflows/squad-promote.yml @@ -13,12 +13,17 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + jobs: dev-to-preview: name: Promote dev → preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -69,8 +74,9 @@ jobs: name: Promote preview → main (release) needs: dev-to-preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/templates/workflows/squad-release.yml b/templates/workflows/squad-release.yml index 6ae0f07fd..b663ac493 100644 --- a/templates/workflows/squad-release.yml +++ b/templates/workflows/squad-release.yml @@ -7,17 +7,23 @@ on: permissions: contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 22 + cache: npm # REQUIRES: package-lock.json (npm). For pnpm: cache: pnpm. For yarn: cache: yarn. - name: Run tests run: node --test test/*.test.cjs diff --git a/templates/workflows/squad-triage.yml b/templates/workflows/squad-triage.yml index d118a2813..fbb097168 100644 --- a/templates/workflows/squad-triage.yml +++ b/templates/workflows/squad-triage.yml @@ -8,15 +8,22 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: false + jobs: triage: - if: github.event.label.name == 'squad' + if: github.event.label.name == 'squad' && github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Triage issue via Lead agent - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); diff --git a/templates/workflows/sync-squad-labels.yml b/templates/workflows/sync-squad-labels.yml index 699fc680f..0ed46b280 100644 --- a/templates/workflows/sync-squad-labels.yml +++ b/templates/workflows/sync-squad-labels.yml @@ -11,14 +11,19 @@ permissions: issues: write contents: read +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + jobs: sync-labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Parse roster and sync labels - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs');