diff --git a/.copilot/skills/protected-files/SKILL.md b/.copilot/skills/protected-files/SKILL.md new file mode 100644 index 000000000..1b35373f9 --- /dev/null +++ b/.copilot/skills/protected-files/SKILL.md @@ -0,0 +1,53 @@ +--- +name: "protected-files" +description: "Zero-dependency bootstrap files that must never import npm packages or SDK code" +domain: "dependency-safety" +confidence: "high" +source: "earned — CLI startup crashes when bootstrap files import SDK code" +--- + +## Context + +The CLI (`squad-cli`) has bootstrap utilities that run **before** the Squad SDK is loaded. If these files import SDK code (e.g., `FSStorageProvider`, anything from `squad-sdk`), the CLI breaks at startup — no helpful error, just a crash. + +This skill applies when: +- Touching any file in `packages/squad-cli/src/cli/core/` +- Running sweeping refactors (e.g., "convert all `fs` calls to `StorageProvider`") +- Adding new bootstrap utilities + +## Protected File List + +| File | Purpose | +|------|---------| +| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` directory at startup — runs before SDK init | +| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) — used by all CLI entry points | +| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper — uses only `node:child_process` and `node:util` | +| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output — pure ANSI codes, zero imports | +| `packages/squad-cli/src/cli/core/history-split.ts` | Separates portable knowledge from project data — pure string logic | + +## Rules + +- ❌ **NEVER** convert these files to use `FSStorageProvider`, `StorageProvider`, or any SDK abstraction +- ❌ **NEVER** add `import` or `require` statements referencing packages outside `node:*` built-ins +- ✅ **ONLY** use `node:fs`, `node:path`, `node:child_process`, `node:util`, and other Node.js built-in modules +- ✅ **DO** check this list before sweeping refactors +- ✅ **LOOK** for `— zero dependencies` markers in file headers as a signal + +## SDK/CLI Package Boundary + +The `packages/squad-cli/src/cli/core/` directory contains a mix of early-startup bootstrap utilities and later SDK-dependent modules. The protected list above is the **authoritative set** of zero-dependency bootstrap files. If you need to add SDK imports to another `core/` file, verify it is not in the protected list and confirm the SDK is loaded at that point in the startup sequence. + +## Anti-Patterns + +- Converting all `fs` calls to `StorageProvider` without checking this list first +- Adding `import { X } from '@bradygaster/squad-sdk'` to a bootstrap file +- Assuming every file in `core/` can safely import SDK code + +## Adding New Bootstrap Utilities + +When adding a new file that runs before SDK init: +1. Add it to the Protected File List table above +2. Write a matching zero-dependency regression test (see `detect-squad-dir-zero-deps.test.ts` for the pattern) +3. Add `— zero dependencies` marker in the file header + +Regression tests guard these files, but **prevention is better than detection**. diff --git a/.github/actions/setup-squad-node/action.yml b/.github/actions/setup-squad-node/action.yml index b1bc0d54a..569d01a5b 100644 --- a/.github/actions/setup-squad-node/action.yml +++ b/.github/actions/setup-squad-node/action.yml @@ -1,6 +1,6 @@ # Composite action: Setup Node.js with npm cache and retry logic # Created to DRY up the repeated setup-node + npm install retry pattern -# used across squad-ci.yml, ci-rerun.yml, squad-npm-publish.yml, +# used across squad-ci.yml, squad-npm-publish.yml, # and squad-insider-publish.yml. # # Usage: diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5271c6c53..d09a2ed3d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,155 +1,60 @@ # Copilot Coding Agent — Squad Instructions -You are working on a project that uses **Squad**, an AI team framework. When picking up issues autonomously, follow these guidelines. - -## Git Safety — Mandatory Rules - -**These rules are non-negotiable. Violating them risks deleting production source code.** - -### Staging -- ❌ **NEVER** use `git add .` or `git add -A` — these stage unintended deletions from incomplete working trees -- ❌ **NEVER** use `git commit -a` — same risk -- ✅ **ALWAYS** stage specific files: `git add path/to/file1.ts path/to/file2.ts` -- ✅ **ALWAYS** review before committing: run `git diff --cached --stat` and verify the file count matches your intent - -### Pushing -- ❌ **NEVER** push directly to `dev` or `main` — always open a PR -- ❌ **NEVER** force push (`git push --force` or `--force-with-lease`) to shared branches -- ✅ **ALWAYS** work on a feature branch: `git checkout -b squad/{issue-number}-{slug}` -- ✅ **ALWAYS** open a PR: `gh pr create --base dev --draft` - -### Pre-Push Checklist -Before pushing any commit, verify: -1. `git diff --cached --stat` — file count matches intent (expect ≤10 files for most fixes) -2. `git diff --cached --diff-filter=D --name-only` — NO unintended deletions -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 -- ANY file deletions you didn't explicitly intend -- Changes outside the scope of your assigned issue - -## Protected Files — Zero External Dependencies - -**Some files MUST only use Node.js built-in modules. Do NOT add npm packages, SDK imports, or any non-`node:*` dependencies to them.** - -These are bootstrap utilities that run **before** the Squad SDK is loaded. If they import SDK code (e.g., `FSStorageProvider`, anything from `squad-sdk`), the CLI breaks at startup. - -### Protected file list -| File | Purpose | -|------|---------| -| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` directory at startup — runs before SDK init | -| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) — used by all CLI entry points | -| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper — uses only `node:child_process` and `node:util` | -| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output — pure ANSI codes, zero imports | -| `packages/squad-cli/src/cli/core/history-split.ts` | Separates portable knowledge from project data — pure string logic | - -### Rules -- ❌ **NEVER** convert these files to use `FSStorageProvider`, `StorageProvider`, or any SDK abstraction -- ❌ **NEVER** add `import` or `require` statements referencing packages outside `node:*` built-ins -- ✅ **ONLY** use `node:fs`, `node:path`, `node:child_process`, `node:util`, and other Node.js built-in modules -- ✅ **DO** check this list before sweeping refactors (e.g., "convert all fs calls to StorageProvider") -- ✅ **LOOK** for `— zero dependencies` markers in file headers as a signal - -### Why this matters -Regression tests guard these files (`detect-squad-dir-zero-deps.test.ts` verifies zero external dependencies), but **prevention is better than detection**. A broken bootstrap means the entire CLI fails to start — no helpful error, just a crash. - -> **When adding new bootstrap utilities**, add them to this table and write a matching zero-dependency regression test. - -### SDK/CLI package boundary -The CLI (`squad-cli`) depends on the SDK (`squad-sdk`). Some CLI files run **before** the SDK is fully loaded. The `packages/squad-cli/src/cli/core/` directory contains a mix of early-startup/bootstrap utilities and later SDK-dependent modules, so treat every file in it with extra caution. The protected list above is the authoritative set of zero-dependency bootstrap files. If you need to add SDK imports to another `core/` file, verify it is not in the protected list above and confirm the SDK is loaded at that point in the startup sequence. - -## Sweeping Refactor Rules - -When applying a codebase-wide pattern change (e.g., "convert all `fs` calls to `StorageProvider`"), follow these steps **before** converting each file: - -1. **Check the Protected Files list above.** If the file is listed, do NOT convert it. -2. **Scan for zero-dependency markers.** Look for `— zero dependencies` in the file's header comment. If present, do NOT convert it. -3. **Verify imports resolve.** When adding `import { X } from '@bradygaster/squad-sdk'`, confirm `X` is actually exported from the SDK's barrel file (`packages/squad-sdk/src/index.ts`). Unresolved imports cause build failures. -4. **Never convert ALL files blindly.** Some files have specific constraints documented in their headers or in this instructions file. Read before you refactor. -5. **Test after each logical group.** Don't convert 30 files and then run the build — convert in small batches and verify each one compiles. +You are working on **Squad**, an AI team framework. Follow these guidelines for autonomous issue work. ## Team Context -Before starting work on any issue: - -1. Read `.squad/team.md` for the team roster, member roles, and your capability profile. +1. Read `.squad/team.md` for roster, roles, and your capability profile. 2. Read `.squad/routing.md` for work routing rules. -3. If the issue has a `squad:{member}` label, read that member's charter at `.squad/agents/{member}/charter.md` to understand their domain expertise and coding style — work in their voice. +3. If the issue has a `squad:{member}` label, read `.squad/agents/{member}/charter.md` — work in their voice. ## Capability Self-Check -Before starting work, check your capability profile in `.squad/team.md` under the **Coding Agent → Capabilities** section. - -- **🟢 Good fit** — proceed autonomously. -- **🟡 Needs review** — proceed, but note in the PR description that a squad member should review. -- **🔴 Not suitable** — do NOT start work. Instead, comment on the issue: - ``` - 🤖 This issue doesn't match my capability profile (reason: {why}). Suggesting reassignment to a squad member. - ``` +Check your profile in `.squad/team.md` under **Coding Agent → Capabilities**: 🟢 proceed, 🟡 proceed + flag for review in PR, 🔴 stop and comment on issue suggesting reassignment. ## Branch Naming -Use the squad branch convention: -``` -squad/{issue-number}-{kebab-case-slug} -``` -Example: `squad/42-fix-login-validation` +`squad/{issue-number}-{kebab-case-slug}` — Example: `squad/42-fix-login-validation` -## PR Guidelines +## Git Safety — Mandatory -When opening a PR: -- Reference the issue: `Closes #{issue-number}` -- If the issue had a `squad:{member}` label, mention the member: `Working as {member} ({role})` -- If this is a 🟡 needs-review task, add to the PR description: `⚠️ This task was flagged as "needs review" — please have a squad member review before merging.` -- Follow any project conventions in `.squad/decisions.md` +- ❌ NEVER `git add .`, `git add -A`, or `git commit -a` — stage specific files only +- ❌ NEVER push to `dev` or `main` directly — always open a PR +- ❌ NEVER force push to shared branches +- ✅ Branch from latest dev: `git fetch origin && git checkout dev && git pull origin dev && git checkout -b ` +- ✅ Before committing: `git diff --cached --stat` (file count matches intent) and `git diff --cached --diff-filter=D --name-only` (no unintended deletions) +- ✅ `npm run build` must pass before pushing. Commit message must reference `Closes #N`. +- 🛑 STOP and ask if: >20 files in diff, unintended deletions, or out-of-scope changes -## PR Review Skills +## Protected Files + +When touching files in `packages/squad-cli/src/cli/core/`, read `.copilot/skills/protected-files/SKILL.md` first. Some bootstrap files must use only Node.js built-ins — no npm packages or SDK imports. -When reviewing or creating PRs, consult these skills for domain-specific checklists: +## Sweeping Refactors -- **Reviewer Protocol:** `.copilot/skills/reviewer-protocol/SKILL.md` — rejection workflow, lockout rules -- **Architectural Review:** `.copilot/skills/architectural-review/SKILL.md` — module boundaries, dependency safety, pattern consistency -- **Security Review:** `.copilot/skills/security-review/SKILL.md` — credentials, injection, workflow permissions, supply chain +Before codebase-wide changes, check the Protected Files skill and scan for `— zero dependencies` markers in file headers. Convert in small batches; verify each compiles. Confirm SDK imports resolve against `packages/squad-sdk/src/index.ts`. -Read the relevant skill(s) before submitting or reviewing a PR that touches their domain. +## PR Guidelines + +- Reference the issue: `Closes #{issue-number}` +- If `squad:{member}` labeled, mention: `Working as {member} ({role})` +- If 🟡 task, add: `⚠️ Needs squad member review before merging.` +- Consult `.squad/decisions.md` for project conventions ## 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. +- **`repo-health`** PRs: Only `.github/`, `scripts/`, root configs, tests, docs. NEVER `packages/*/src/`. +- **`fix`/`feat`** PRs: May modify product source. Requires changeset if touching `packages/*/src/`. +- Split infrastructure + product changes into separate PRs. ## Changeset Requirement -Any PR that modifies files under `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a changeset file. +PRs modifying `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a `.changeset/{name}.md` file (patch/minor/major). The `changelog-gate` CI check enforces this. Escape hatch: `skip-changelog` label. + +## PR Review Skills -- 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) +Before submitting or reviewing PRs, consult: `.copilot/skills/reviewer-protocol/SKILL.md`, `.copilot/skills/architectural-review/SKILL.md`, `.copilot/skills/security-review/SKILL.md`. ## Decisions -If you make a decision that affects other team members, write it to: -``` -.squad/decisions/inbox/copilot-{brief-slug}.md -``` -The Scribe will merge it into the shared decisions file. +Team decisions go to `.squad/decisions/inbox/copilot-{brief-slug}.md` — Scribe merges them. diff --git a/.github/workflows/ci-rerun.yml b/.github/workflows/ci-rerun.yml deleted file mode 100644 index 3652ce909..000000000 --- a/.github/workflows/ci-rerun.yml +++ /dev/null @@ -1,153 +0,0 @@ -# CI Rerun — Manual workflow for re-running CI on fork PRs or PRs that need -# a fresh CI pass without push. Useful when: -# - Fork PRs don't trigger CI automatically (security restriction) -# - A PR needs re-testing after an infrastructure flake -# - You need to validate a PR against current base branch state -# Usage: Actions → CI Rerun (Manual) → enter PR number → Run workflow -name: CI Rerun (Manual) - -on: - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to test' - required: true - type: string - -permissions: - contents: read - statuses: write - -jobs: - test: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Fetch PR metadata - id: pr - env: - GH_TOKEN: ${{ github.token }} - run: | - PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}) - HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') - HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref') - HEAD_REPO=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') - BASE_REF=$(echo "$PR_DATA" | jq -r '.base.ref') - echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" - echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" - echo "head_repo=$HEAD_REPO" >> "$GITHUB_OUTPUT" - echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT" - echo "Testing PR #${{ inputs.pr_number }}: $HEAD_REPO/$HEAD_REF → $BASE_REF (SHA: $HEAD_SHA)" - - - name: Set pending status - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=pending \ - --field context="Squad CI / rerun" \ - --field description="Running CI rerun..." \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - - uses: actions/checkout@v4 - with: - ref: refs/pull/${{ inputs.pr_number }}/merge - continue-on-error: true - id: checkout_merge - - - name: Fallback — checkout PR head directly - if: steps.checkout_merge.outcome == 'failure' - uses: actions/checkout@v4 - with: - repository: ${{ steps.pr.outputs.head_repo }} - ref: ${{ steps.pr.outputs.head_ref }} - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - - name: Fix stale lockfile entries - run: | - node -e " - const fs = require('fs'); - const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); - const pkgs = lock.packages || {}; - const stale = Object.keys(pkgs).filter(k => - k.includes('/node_modules/@bradygaster/squad-') && - pkgs[k].resolved && pkgs[k].resolved.startsWith('https://') - ); - if (stale.length) { - stale.forEach(k => { console.log('Removing: ' + k); delete pkgs[k]; }); - fs.writeFileSync('package-lock.json', JSON.stringify(lock, null, 2) + '\n'); - } else { - console.log('Lockfile clean'); - } - " - - - name: Install dependencies - run: | - success=false - for i in 1 2 3; do - if npm install; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 - done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - - - name: Install docs dependencies - run: | - success=false - for i in 1 2 3; do - if npm ci; then - success=true - break - fi - echo "Retry $i/3 — npm ci failed, retrying in 5s..." - sleep 5 - done - if [ "$success" = false ]; then - echo "::error::npm ci failed after 3 attempts" - exit 1 - fi - working-directory: docs - - - name: Install Playwright browsers - run: npx playwright install chromium --with-deps - - - name: Build - run: npm run build - - - name: Run tests - run: npm test - - - name: Set success status - if: success() - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=success \ - --field context="Squad CI / rerun" \ - --field description="All checks passed" \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - - name: Set failure status - if: failure() - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=failure \ - --field context="Squad CI / rerun" \ - --field description="CI rerun failed — check logs" \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 0dffd2f1f..4fb2483f9 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -9,22 +9,23 @@ on: permissions: contents: read + pull-requests: read + pull-requests: read -# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +# Prevent parallel runs from competing for resources concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ── Path filter for conditional job execution ────────────────────────── - # Detects which areas of the repo changed so downstream jobs can skip - # when their inputs haven't changed (e.g., skip docs-quality on code-only PRs). changes: runs-on: ubuntu-latest timeout-minutes: 3 outputs: docs: ${{ steps.filter.outputs.docs }} code: ${{ steps.filter.outputs.code }} + workflows: ${{ steps.filter.outputs.workflows }} steps: - uses: actions/checkout@v4 with: @@ -45,12 +46,16 @@ jobs: else echo "docs=false" >> "$GITHUB_OUTPUT" fi - # Detect non-docs changes (code, config, workflows, etc.) if echo "$CHANGED" | grep -qvE '^(docs/|README\.md|\.markdownlint|\.cspell|cspell\.json)'; then echo "code=true" >> "$GITHUB_OUTPUT" else echo "code=false" >> "$GITHUB_OUTPUT" fi + if echo "$CHANGED" | grep -qE '^\.github/workflows/'; then + echo "workflows=true" >> "$GITHUB_OUTPUT" + else + echo "workflows=false" >> "$GITHUB_OUTPUT" + fi docs-quality: needs: changes @@ -59,30 +64,19 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 22 - - name: Install docs tools run: | success=false for i in 1 2 3; do - if npm install --no-save markdownlint-cli2 cspell; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 + if npm install --no-save markdownlint-cli2 cspell; then success=true; break; fi + echo "Retry $i/3 — npm install failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - + [ "$success" = true ] || { echo "::error::npm install failed after 3 attempts"; exit 1; } - name: Lint docs markdown run: npx markdownlint-cli2 - - name: Spell check docs run: npx cspell --no-progress --dot "docs/src/content/**/*.md" "README.md" @@ -98,12 +92,10 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - - name: Fix stale lockfile entries run: | node -e " @@ -121,46 +113,27 @@ jobs: console.log('Lockfile clean'); } " - - name: Install dependencies run: | success=false for i in 1 2 3; do - if npm install; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 + if npm install; then success=true; break; fi + echo "Retry $i/3 — npm install failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - + [ "$success" = true ] || { echo "::error::npm install failed after 3 attempts"; exit 1; } - name: Install docs dependencies run: | success=false for i in 1 2 3; do - if npm ci; then - success=true - break - fi - echo "Retry $i/3 — npm ci failed, retrying in 5s..." - sleep 5 + if npm ci; then success=true; break; fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm ci failed after 3 attempts" - exit 1 - fi + [ "$success" = true ] || { echo "::error::npm ci failed after 3 attempts"; exit 1; } working-directory: docs - - name: Install Playwright browsers run: npx playwright install chromium --with-deps - - name: "🔒 Source tree canary check" run: | - echo "Verifying critical source files exist..." MISSING=0 for f in \ "packages/squad-sdk/src/index.ts" \ @@ -177,7 +150,6 @@ jobs: exit 1 fi echo "✅ All critical source files present" - - name: "🔒 Large deletion guard" if: github.event_name == 'pull_request' run: | @@ -194,37 +166,17 @@ jobs: fi env: GH_TOKEN: ${{ github.token }} - - name: Build run: npm run build - - name: Run tests run: npm test - # ════════════════════════════════════════════════════════════════════════ - # Skip Labels Reference - # ──────────────────────────────────────────────────────────────────────── - # The following PR labels can be used to bypass specific health gates. - # Add them via the GitHub UI or `gh pr edit --add-label