name: Packages Checks

on:
  pull_request:
    paths:
      - 'packages/**'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - 'pnpm-workspace.yaml'
      - '.github/workflows/packages-checks.yml'
      - '.github/dependabot.yml'
      - 'scripts/validation/detect-pr-changes.ts'
      - 'scripts/validation/detect-pr-changes.test.ts'
      # The canonical source, its committed output, and the drift gate's machinery.
      - 'content/**'
      - '.opencode/agent/**'
      - '.oac/**'
      - 'registry.json'
      - 'Makefile'
      - 'scripts/validation/check-build-drift.sh'

permissions:
  contents: read

jobs:
  check-changes:
    name: Detect Package Changes
    runs-on: ubuntu-latest
    outputs:
      has-packages: ${{ steps.filter.outputs.has-packages }}
      has-canonical: ${{ steps.filter.outputs.has-canonical }}

    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Setup Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
        with:
          bun-version: 1.3.14

      # The detector decides which gates run, so a broken detector silently skips them —
      # including the drift gate. Its tests also pin the drift job's contract (that it is
      # blocking, gated on has-canonical, and runs the same make target contributors do).
      # They must execute somewhere, and nothing else in CI ran them.
      - name: Test change detection
        run: bun test scripts/validation/detect-pr-changes.test.ts

      - name: Check changed files
        id: filter
        run: |
          echo "Changed files:" >&2
          git diff --name-only -z "${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}" |
            tee >(while IFS= read -r -d '' path; do printf '  %q\n' "$path" >&2; done) |
            bun run scripts/validation/detect-pr-changes.ts

  cli-checks:
    name: CLI (build + test + typecheck)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: check-changes
    if: needs.check-changes.outputs.has-packages == 'true'

    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false

      - name: Setup Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
        with:
          bun-version: 1.3.14

      - name: Setup pnpm
        uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6

      - name: Setup Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: 'pnpm-lock.yaml'

      - name: Install workspace dependencies
        run: pnpm install --frozen-lockfile

      # cli tsconfig paths point at ../compatibility-layer/dist, so build it first.
      - name: Build compatibility-layer dependency
        run: pnpm --dir packages/compatibility-layer run build

      - name: Typecheck
        run: pnpm --dir packages/cli run typecheck

      - name: Build
        run: pnpm --dir packages/cli run build

      # packages/cli tests use Bun-only APIs (Bun.file, Bun.spawn, import.meta.dir)
      # until Stage 5 removes them — they must run under `bun test`, not vitest.
      - name: Test
        run: pnpm --dir packages/cli run test

  compatibility-layer-checks:
    name: Compatibility Layer (build + test + lint)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: check-changes
    if: needs.check-changes.outputs.has-packages == 'true'

    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false

      - name: Setup pnpm
        uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6

      - name: Setup Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: 'pnpm-lock.yaml'

      - name: Install workspace dependencies
        run: pnpm install --frozen-lockfile

      # build runs tsc, which is also the typecheck for this package.
      - name: Build (tsc typecheck)
        run: pnpm --dir packages/compatibility-layer run build

      - name: Lint
        run: pnpm --dir packages/compatibility-layer run lint

      - name: Test (vitest)
        run: pnpm --dir packages/compatibility-layer test

  # The structural guarantee the canonical refactor rests on: the generated trees stay
  # COMMITTED, so drift has to be impossible rather than merely discouraged. Rebuild them from
  # content/agents/** and fail if the build changed anything — a hand-edit to generated output
  # turns this red. BLOCKING by design: no continue-on-error.
  canonical-drift:
    name: Canonical Build (no drift)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: check-changes
    if: needs.check-changes.outputs.has-canonical == 'true'

    steps:
      - name: Checkout code
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          persist-credentials: false

      # `oac build` runs from packages/cli/dist, which is bundled `--target bun`.
      - name: Setup Bun
        uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
        with:
          bun-version: 1.3.14

      - name: Setup pnpm
        uses: pnpm/action-setup@b0f76dfb45f55f8421693e4803ac7bb65143bd34 # v6

      - name: Setup Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: '20'
          cache: 'pnpm'
          cache-dependency-path: 'pnpm-lock.yaml'

      - name: Install workspace dependencies
        run: pnpm install --frozen-lockfile

      # The identical target contributors run locally, so a red gate here is reproducible with
      # one command rather than by reading this file and reassembling it by hand.
      - name: Build canonical trees and check for drift
        run: make check-canonical-drift

  summary:
    name: Packages Checks Summary
    runs-on: ubuntu-latest
    needs: [check-changes, cli-checks, compatibility-layer-checks, canonical-drift]
    if: always()

    steps:
      - name: Generate summary
        run: |
          echo "## 📦 Packages Checks Summary" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY

          report() {
            local label="$1" result="$2"
            case "$result" in
              success) echo "✅ **${label}:** Passed" >> $GITHUB_STEP_SUMMARY ;;
              skipped) echo "⏭️ **${label}:** Skipped (no relevant changes)" >> $GITHUB_STEP_SUMMARY ;;
              *) echo "❌ **${label}:** ${result}" >> $GITHUB_STEP_SUMMARY ;;
            esac
          }

          report "Change Detection" "${{ needs.check-changes.result }}"
          report "CLI" "${{ needs.cli-checks.result }}"
          report "Compatibility Layer" "${{ needs.compatibility-layer-checks.result }}"
          report "Canonical Build (no drift)" "${{ needs.canonical-drift.result }}"

          echo "" >> $GITHUB_STEP_SUMMARY

          if [ "${{ needs.canonical-drift.result }}" = "failure" ]; then
            echo "> The generated trees drift from \`content/agents/**\`." >> $GITHUB_STEP_SUMMARY
            echo "> Run \`make build-canonical\` and commit the result." >> $GITHUB_STEP_SUMMARY
            echo "" >> $GITHUB_STEP_SUMMARY
          fi

          FAILED=0
          [ "${{ needs.check-changes.result }}" != "success" ] && FAILED=1
          for result in "${{ needs.cli-checks.result }}" "${{ needs.compatibility-layer-checks.result }}" "${{ needs.canonical-drift.result }}"; do
            if [ "$result" != "success" ] && [ "$result" != "skipped" ]; then
              FAILED=1
            fi
          done

          if [ $FAILED -eq 0 ]; then
            echo "### ✅ Required Package Checks Passed" >> $GITHUB_STEP_SUMMARY
          else
            echo "### ❌ Some Package Checks Failed" >> $GITHUB_STEP_SUMMARY
            exit 1
          fi
