> ## Documentation Index
> Fetch the complete documentation index at: https://bintzgavin-apastra-14.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# apastra-setup-ci

> Upgrade from local-first evaluation to automated GitHub Actions CI. Adds PR gating, regression blocking, governed releases, and approval tracking to your repository.

## Installation

```bash theme={null}
npx skills add BintzGavin/apastra/skills/setup-ci
```

## When to use

Apastra is designed to be **CI optional**. The local-first workflow — where your IDE agent runs evals and manages baselines — is sufficient for individual developers.

Upgrade to CI when:

<CardGroup cols={2}>
  <Card title="Team scaling" icon="users">
    Multiple contributors are editing prompts and you need a consistent quality gate on every pull request.
  </Card>

  <Card title="PR gating" icon="shield">
    You want to block merges automatically when a prompt change causes a regression against the established baseline.
  </Card>

  <Card title="Governed releases" icon="lock">
    You need an immutable, auditable record of which prompt package was shipped and who approved it.
  </Card>

  <Card title="Autonomous agents" icon="robot">
    Autonomous coding agents are submitting PRs and you need automated quality checks before any merge.
  </Card>
</CardGroup>

<Note>
  The core file-based protocol never changes when you upgrade to CI. Prompts, datasets, evaluators, suites, and baselines stay exactly the same — CI just adds an automated layer on top.
</Note>

## How to invoke

Ask your agent:

> "Use the apastra-setup-ci skill to set up CI for this repository"

Your agent copies pre-built GitHub Actions workflows into `.github/workflows/`.

## The two CI tiers

<Tabs>
  <Tab title="Basic CI (2 workflows)">
    The minimum setup for PR gating. Two workflows working together:

    **`prompt-eval.yml`** — triggers on pull requests that touch `promptops/**` and delegates to `regression-gate.yml`:

    ```yaml theme={null}
    name: Prompt Eval
    on:
      pull_request:
        paths:
          - 'promptops/**'

    jobs:
      eval:
        uses: ./.github/workflows/regression-gate.yml
    ```

    **`prompt-release.yml`** — triggers on tag pushes and delegates to `immutable-release.yml`:

    ```yaml theme={null}
    name: Prompt Release
    on:
      push:
        tags:
          - '*'

    jobs:
      release:
        uses: ./.github/workflows/immutable-release.yml
    ```
  </Tab>

  <Tab title="Full CI (6 workflows)">
    The complete governance stack. All six workflows:

    | Workflow                | Trigger                              | What it does                                                  |
    | ----------------------- | ------------------------------------ | ------------------------------------------------------------- |
    | `regression-gate.yml`   | Pull requests to `main`              | Checks for regressions; blocks merge on failure               |
    | `auto-merge.yml`        | PR opened, reopened, or synchronized | Auto-merges PRs from approved agents once all checks pass     |
    | `promote.yml`           | Manual dispatch or release published | Records an approved promotion to a delivery channel           |
    | `deliver.yml`           | Called by `promote.yml`              | Syncs promoted prompt versions to downstream delivery targets |
    | `immutable-release.yml` | Tag push                             | Packages `promptops/` and creates an immutable GitHub Release |
    | `record-approval.yml`   | Manual dispatch                      | Records a human reviewer's approval or rejection decision     |
  </Tab>
</Tabs>

## Workflow details

<AccordionGroup>
  <Accordion title="regression-gate.yml">
    **Trigger:** Pull requests to `main`; also callable as a reusable workflow.

    **What it does:**

    1. Checks out the repository with the last two commits
    2. Detects whether any files in `promptops/harnesses/`, `promptops/prompts/`, `promptops/datasets/`, or `promptops/policies/` changed — if none changed, it skips
    3. Fetches the `promptops-artifacts` branch and reads `reports/regression_report.json` and `reports/run_manifest.json`
    4. If the regression report is missing, blocks the merge with an error
    5. Renders a summary table of all evidence items (metric, status, candidate, baseline, delta, message) into the GitHub step summary
    6. Checks each evidence item for `"status": "fail"` and emits individual GitHub error annotations
    7. Reads the final `.status` field — if `"pass"`, checks the run manifest for a `total_cost` and compares it against any `budgets.cost_budget` defined in suite files; if over budget, blocks the merge
    8. Exits 0 on pass, exits 1 on any failure

    **Effect:** No PR touching a prompt, dataset, harness, or policy can merge without a passing regression report on the `promptops-artifacts` branch.
  </Accordion>

  <Accordion title="auto-merge.yml">
    **Trigger:** Pull requests opened, reopened, or synchronized.

    **What it does:**

    1. Runs only for PRs from approved authors (configurable in the workflow file)
    2. Fetches the base branch and attempts a merge, automatically resolving `PROGRESS.md` conflicts using a union merge strategy
    3. Pushes any resolved conflicts back to the PR branch
    4. Enables GitHub's auto-merge (squash strategy) via `gh pr merge --auto --squash`
    5. Retries up to 5 times with backoff to handle GitHub API rate limits

    **Effect:** Once all required checks pass (including `regression-gate`), the PR merges automatically without manual intervention.
  </Accordion>

  <Accordion title="promote.yml">
    **Trigger:** Manual workflow dispatch (with `digest`, `channel`, and `evidence_refs` inputs) or release published.

    **What it does:**

    1. Checks out the `promptops-artifacts` branch
    2. Verifies that an approval record exists in the `approvals/` directory matching the target digest and `"decision": "approved"` — blocks promotion if no approval is found
    3. Generates a promotion record JSON and appends it to `derived-index/promotions/`:

    ```json theme={null}
    {
      "id": "<uuid>",
      "timestamp": "2026-03-16T09:00:00Z",
      "digest": "<content digest>",
      "channel": "prod",
      "approver": "<github-actor>",
      "evidence_refs": ["<uri-to-regression-report>"]
    }
    ```

    4. Commits the record and calls `deliver.yml` with the promotion record path

    **Effect:** Every promotion to a channel is recorded as an append-only artifact, traceable back to the evidence that justified it.
  </Accordion>

  <Accordion title="deliver.yml">
    **Trigger:** Called by `promote.yml` after a promotion record is committed.

    **What it does:**

    1. Fetches the promotion record from the `promptops-artifacts` branch
    2. Reads the `channel` and `digest` from the record
    3. Iterates over all files in `promptops/delivery/*.yaml`, finds those matching the promotion channel, and executes the sync for the configured target type and repository

    **Effect:** Approved and promoted prompt versions are automatically pushed to whatever delivery targets (config stores, edge endpoints, etc.) you configure in `promptops/delivery/`.
  </Accordion>

  <Accordion title="immutable-release.yml">
    **Trigger:** Tag push (any tag); also callable as a reusable workflow.

    **What it does:**

    1. Packages the entire `promptops/` directory as `promptops.tar.gz`
    2. Computes a SHA-256 digest of the archive
    3. Attests build provenance using `actions/attest-build-provenance` (creates a verifiable SLSA provenance record)
    4. Creates a GitHub Release with the archive attached and the digest in the release notes

    ```bash theme={null}
    gh release create "$TAG_NAME" promptops.tar.gz \
      --title "Release $TAG_NAME" \
      --notes "Immutable release for $TAG_NAME. Digest: $DIGEST" \
      --generate-notes
    ```

    **Effect:** Every tagged release of your prompt package is cryptographically attested and cannot be tampered with after publication.
  </Accordion>

  <Accordion title="record-approval.yml">
    **Trigger:** Manual workflow dispatch with `revision_ref` (the target digest or ID) and `decision` (`approved`, `rejected`, or `abstained`).

    **What it does:**

    1. Checks out the `promptops-artifacts` branch
    2. Generates an approval state record and appends it to `approvals/`:

    ```json theme={null}
    {
      "revision_ref": "<digest>",
      "checks_passed": true,
      "decision": "approved",
      "human_review": {
        "reviewer": "<github-actor>",
        "timestamp": "2026-03-16T09:00:00Z"
      }
    }
    ```

    3. Commits the record

    **Effect:** Human reviewer decisions are stored as append-only records, which `promote.yml` checks before allowing any promotion.
  </Accordion>
</AccordionGroup>

## Configuring required status checks

After installing the workflows, protect your main branch:

<Steps>
  <Step title="Open branch protection settings">
    In your GitHub repository, go to **Settings** > **Branches**.
  </Step>

  <Step title="Add a protection rule">
    Add a branch protection rule for your main branch.
  </Step>

  <Step title="Enable required status checks">
    Enable **Require status checks to pass before merging** and add the `gate` job (from `regression-gate.yml`) to the required checks list.
  </Step>
</Steps>

With this in place, no one — including autonomous agents — can merge a prompt change that drops quality below the established baseline.

## What stays the same after upgrading

Upgrading to CI does not change your local workflow:

* You still edit prompts in `promptops/prompts/`
* You still write cases in `promptops/datasets/`
* You still test locally using the `apastra-eval` skill
* The only difference: when you push a PR, GitHub runs the regression gate automatically

<Tip>
  Start with basic CI (just `prompt-eval.yml` and `prompt-release.yml`) and add the full governance stack when your team is ready for it. There is no need to set up all six workflows at once.
</Tip>
