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: 20 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js ${{ matrix.node }} uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node }} # Package manager setup - name: Setup pnpm if: matrix.pm == 'pnpm' && matrix.node != '18.x' uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 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 # 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' npm_config_ignore_scripts: 'true' YARN_ENABLE_SCRIPTS: 'false' run: | case "${{ matrix.pm }}" in npm) npm ci --ignore-scripts ;; # 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 --ignore-scripts --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 --mode=skip-build ;; bun) bun install --ignore-scripts ;; *) 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/ pack-installer: name: Pack Installer Artifact runs-on: ubuntu-latest timeout-minutes: 10 outputs: package_file: ${{ steps.pack.outputs.package_file }} package_sha256: ${{ steps.pack.outputs.package_sha256 }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20.x' - name: Install dependencies run: npm ci --ignore-scripts - name: Pack exact installer artifact id: pack run: | npm pack --json > npm-pack.json node -e "const crypto = require('crypto'); const fs = require('fs'); const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8')); const file = data[0]?.filename; if (!/^ecc-universal-[0-9A-Za-z.+-]+\.tgz$/.test(file || '')) throw new Error('Unexpected packed filename'); const archives = fs.readdirSync('.').filter(name => name.endsWith('.tgz')); if (archives.length !== 1 || archives[0] !== file) throw new Error('Expected exactly one packed archive'); const digest = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); fs.appendFileSync(process.env.GITHUB_OUTPUT, 'package_file=' + file + '\npackage_sha256=' + digest + '\n')" - name: Upload exact installer artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ecc-ci-installer-artifact path: ${{ steps.pack.outputs.package_file }} if-no-files-found: error packed-install-lifecycle: name: Packed Install (${{ matrix.os }}) needs: pack-installer runs-on: ${{ matrix.os }} timeout-minutes: 15 strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout lifecycle test uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20.x' - name: Download exact installer artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ecc-ci-installer-artifact path: release-artifacts - name: Verify packed install lifecycle env: ECC_RELEASE_PACKAGE: release-artifacts/${{ needs.pack-installer.outputs.package_file }} ECC_RELEASE_SHA256: ${{ needs.pack-installer.outputs.package_sha256 }} run: node tests/ci/packed-artifact-lifecycle.js validate: name: Validate Components runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.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: Validate command registry run: npm run command-registry:check 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 python-tests: name: Python Lint, Type Check & Test runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.11' - name: Install Python dependencies run: python -m pip install --upgrade pip && python -m pip install -e '.[dev]' - name: Run ruff (lint) run: python -m ruff check src tests - name: Run mypy (type check) run: python -m mypy src - name: Run Python tests run: python -m pytest tests/test_*.py -m "not integration" security: name: Security Scan runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20.x' - name: Install audit dependencies run: npm ci --ignore-scripts - name: Run npm audit run: | npm audit signatures # Runtime/package advisories are release blockers. Development-only # lint tooling remains covered by signature and IOC verification. npm audit --omit=dev --audit-level=high - name: Run supply-chain IOC scan run: npm run security:ioc-scan coverage: name: Coverage runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20.x' - name: Install dependencies run: npm ci --ignore-scripts - name: Run coverage run: npm run coverage - name: Upload coverage report if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage-ubuntu-node20-npm path: coverage/ lint: name: Lint runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '20.x' - name: Install dependencies run: npm ci --ignore-scripts - name: Run lint run: npm run lint