name: CI

on:
  push:
    branches: [main, 'release/**']
    tags: ['v*']
  pull_request:
    branches: [main]

# Prevent duplicate runs
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Minimal permissions
permissions:
  contents: read

jobs:
  test:
    name: Test (${{ matrix.os }}, Node ${{ matrix.node }}, ${{ matrix.pm }})
    runs-on: ${{ matrix.os }}
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: ['18.x', '20.x', '22.x']
        pm: [npm, pnpm, yarn, bun]
        exclude:
          # Bun has limited Windows support
          - os: windows-latest
            pm: bun

    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Node.js ${{ matrix.node }}
        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: ${{ matrix.node }}

      # Package manager setup
      - name: Setup pnpm
        if: matrix.pm == 'pnpm' && matrix.node != '18.x'
        uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
        with:
          # Keep an explicit pnpm major because this repo's packageManager is Yarn.
          version: 10

      - name: Setup pnpm (via Corepack)
        if: matrix.pm == 'pnpm' && matrix.node == '18.x'
        shell: bash
        run: |
          corepack enable
          corepack prepare pnpm@9 --activate

      - name: Setup Yarn (via Corepack)
        if: matrix.pm == 'yarn'
        shell: bash
        run: |
          corepack enable
          corepack prepare yarn@stable --activate

      - name: Setup Bun
        if: matrix.pm == 'bun'
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2

      # Cache configuration
      - name: Get npm cache directory
        if: matrix.pm == 'npm'
        id: npm-cache-dir
        shell: bash
        run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT

      - name: Cache npm
        if: matrix.pm == 'npm'
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-${{ matrix.node }}-npm-

      - name: Get pnpm store directory
        if: matrix.pm == 'pnpm'
        id: pnpm-cache-dir
        shell: bash
        env:
          COREPACK_ENABLE_STRICT: '0'
        run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT

      - name: Cache pnpm
        if: matrix.pm == 'pnpm'
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ${{ steps.pnpm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-node-${{ matrix.node }}-pnpm-

      - name: Get yarn cache directory
        if: matrix.pm == 'yarn'
        id: yarn-cache-dir
        shell: bash
        run: |
          # Try Yarn Berry first, fall back to Yarn v1
          if yarn config get cacheFolder >/dev/null 2>&1; then
            echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
          else
            echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
          fi

      - name: Cache yarn
        if: matrix.pm == 'yarn'
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ${{ steps.yarn-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-node-${{ matrix.node }}-yarn-

      - name: Cache bun
        if: matrix.pm == 'bun'
        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
        with:
          path: ~/.bun/install/cache
          key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
          restore-keys: |
            ${{ runner.os }}-bun-

      # Install dependencies
      # COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
      # package.json declares "packageManager": "yarn@..."
      - name: Install dependencies
        shell: bash
        env:
          COREPACK_ENABLE_STRICT: '0'
        run: |
          case "${{ matrix.pm }}" in
            npm) npm ci ;;
            # pnpm v10 can fail CI on ignored native build scripts
            # (for example msgpackr-extract) even though this repo is Yarn-native
            # and pnpm is only exercised here as a compatibility lane.
            pnpm) pnpm install --config.strict-dep-builds=false --no-frozen-lockfile ;;
            # Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
            yarn) yarn install ;;
            bun) bun install ;;
            *) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
          esac

      # Run tests
      - name: Run tests
        run: node tests/run-all.js
        env:
          CLAUDE_CODE_PACKAGE_MANAGER: ${{ matrix.pm }}

      # Upload test artifacts on failure
      - name: Upload test artifacts
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: test-results-${{ matrix.os }}-node${{ matrix.node }}-${{ matrix.pm }}
          path: |
            tests/
            !tests/node_modules/

  validate:
    name: Validate Components
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Node.js
        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: '20.x'

      - name: Install validation dependencies
        run: npm ci --ignore-scripts

      - name: Validate agents
        run: node scripts/ci/validate-agents.js
        continue-on-error: false

      - name: Validate hooks
        run: node scripts/ci/validate-hooks.js
        continue-on-error: false

      - name: Validate commands
        run: node scripts/ci/validate-commands.js
        continue-on-error: false

      - name: Validate skills
        run: node scripts/ci/validate-skills.js
        continue-on-error: false

      - name: Validate install manifests
        run: node scripts/ci/validate-install-manifests.js
        continue-on-error: false

      - name: Validate workflow security
        run: node scripts/ci/validate-workflow-security.js
        continue-on-error: false

      - name: Validate rules
        run: node scripts/ci/validate-rules.js
        continue-on-error: false

      - name: Validate catalog counts
        run: node scripts/ci/catalog.js --text
        continue-on-error: false

      - name: Check unicode safety
        run: node scripts/ci/check-unicode-safety.js
        continue-on-error: false

      - name: Validate no personal paths
        run: node scripts/ci/validate-no-personal-paths.js
        continue-on-error: false

  security:
    name: Security Scan
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Node.js
        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: '20.x'

      - name: Run npm audit
        run: npm audit --audit-level=high
        continue-on-error: true  # Allows PR to proceed, but marks job as failed if vulnerabilities found

  lint:
    name: Lint
    runs-on: ubuntu-latest
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

      - name: Setup Node.js
        uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
        with:
          node-version: '20.x'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npx eslint scripts/**/*.js tests/**/*.js

      - name: Run markdownlint
        run: npx markdownlint "agents/**/*.md" "skills/**/*.md" "commands/**/*.md" "rules/**/*.md"
