name: CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  setup:
    name: Setup
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

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

      - name: Build
        run: pnpm build


  changeset-check:
    name: Changeset check
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Check for changeset
        env:
          BASE_REF: ${{ github.event.pull_request.base.ref }}
        run: |
          # Paths that require a changeset when modified
          VERSIONED_PATHS='extensions/|skills/|agents/|package\.json|plugin\.json'

          changed=$(git diff --name-only "origin/${BASE_REF}...HEAD")
          echo "Changed files:"
          echo "$changed"

          versioned=$(echo "$changed" | grep -E "$VERSIONED_PATHS" || true)
          if [ -z "$versioned" ]; then
            echo "No versioned paths changed — skipping changeset check."
            exit 0
          fi

          changesets=$(echo "$changed" | grep -E '^\.changeset/.*\.md$' | grep -v 'README.md' || true)
          if [ -z "$changesets" ]; then
            echo ""
            echo "::error::This PR modifies versioned paths but does not include a changeset."
            echo "Run 'pnpm changeset' to add one. See: style-versioning skill."
            echo ""
            echo "Versioned paths changed:"
            echo "$versioned"
            exit 1
          fi

          echo "Changeset found: $changesets"

  lint:
    name: Lint
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

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

      - name: Lint
        run: pnpm lint

  test:
    name: Test
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

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

      - name: Test
        run: pnpm test

  claude-plugin-check:
    name: Claude plugin materialiser check
    needs: setup
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

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

      # The claude/ subtree is retired — the installer is the only generator
      # (plain `agent-cortex install claude` materialises ~/.agent-cortex/claude;
      # `--output <dir>` is generate-only for tests). There is no committed
      # output to diff, so this job validates the materialiser itself: it must
      # produce a complete, structural plugin (generated + hand-authored pieces),
      # no symlinks, no literal {{TOOL:/{{PATH:/{{SECTION: tokens, and a
      # plugin.json version tracking the package — exactly what a real install
      # would ship.
      - name: Materialise the Claude plugin and run structural checks
        run: |
          set -euo pipefail
          OUT="$RUNNER_TEMP/claude-plugin-check"
          node bin/agent-cortex.mjs install claude --output "$OUT"

          # structure: generated + hand-authored pieces all present
          test -f "$OUT/.claude-plugin/plugin.json"
          test -f "$OUT/agents/plan.md"
          test -f "$OUT/agents/ralph.md"
          test -f "$OUT/skills/tdd/SKILL.md"
          test -f "$OUT/hooks.json"
          test -f "$OUT/hooks/scripts/notify.mjs"
          test -f "$OUT/.mcp.json"
          test -f "$OUT/scripts/statusline-command.sh"

          # flat-copy guarantee: no symlinks in the materialised tree
          if [ -n "$(find "$OUT" -type l)" ]; then
            echo "::error::symlinks found in materialised Claude plugin"
            exit 1
          fi

          # token substitution: no literal tokens survive materialisation
          TOKENS=$(grep -rlE '\{\{(TOOL|PATH|SECTION):' "$OUT" || true)
          if [ -n "$TOKENS" ]; then
            echo "::error::literal substitution tokens in materialised Claude plugin:"
            echo "$TOKENS"
            exit 1
          fi

          # plugin version tracks the package (never stale)
          PKG_VERSION=$(node -p "require('./package.json').version")
          PLUGIN_VERSION=$(node -p "require('$OUT/.claude-plugin/plugin.json').version")
          test "$PKG_VERSION" = "$PLUGIN_VERSION"

      - name: Check generated Copilot agent files are up to date
        run: |
          pnpm build:copilot
          git diff --exit-code -- 'agents/*.agent.md'

  release:
    name: Release
    needs: [lint, test, claude-plugin-check]
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
      # npm Trusted Publishing (OIDC): npm exchanges this id-token for a
      # short-lived npm token at publish time — no token config or secret.
      id-token: write
    concurrency: ${{ github.workflow }}-${{ github.ref }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Cache pnpm store
        uses: actions/cache@v4
        with:
          path: ~/.pnpm-store
          key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false

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

      - name: Create Release Pull Request or Publish
        id: changesets
        uses: changesets/action@v1
        with:
          # changesets/action execs these inputs via @actions/exec with NO shell:
          # the string is split by argStringToArray on whitespace (single quotes
          # survive literally), so each must be a single unquoted command token
          # that runs a pnpm script. version-packages bumps versions, syncs
          # plugin.json, THEN regenerates the committed Copilot output
          # (build:copilot re-derives agents/*.agent.md; the Claude plugin is
          # materialised at install time with the package version, so nothing
          # committed needs regenerating) before the action commits, so the
          # Version Packages PR always passes the drift gates.
          version: pnpm version-packages
          publish: pnpm publish-package
          commit: "chore: version packages"
          title: "chore: version packages"
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # Publish authenticates via npm Trusted Publishing (OIDC token
          # exchange at publish time) — no NPM_TOKEN secret, no token config.
          # Requires the one-time npm-side setup: npmjs.com → @jaybeeuu/
          # agent-cortex → Access → Trusted Publishing, tied to GitHub repo
          # jaybeeuu/agent-cortex + workflow .github/workflows/ci.yml.
