> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paradime.io/llms.txt
> Use this file to discover all available pages before exploring further.

# End-to-end PR reviewer

> Automate end-to-end pull request reviews with a DinoAI agent that checks PR scope against Linear, validates dbt™ code, tests, and docs, and posts a structured verdict to Slack.

Automate your analytics engineering code reviews with a DinoAI agent that acts like a thorough senior reviewer — reading the PR diff, checking the linked Linear issue, verifying test and documentation coverage, and posting a structured verdict directly to Slack.

<Info>
  **Before You Start**

  **Paradime**

  * Your Paradime API endpoint, API key, and API secret. — You can generate under [Workspce Settings → API](/developers/generate-api-keys-legacy). Make sure to have `DinoAI agent API` capabilities. Requires Admin access to generate API keys.

  **GitHub**

  * Write access to the repository you want to review PRs on
  * Ability to add repository secrets and create GitHub Actions workflows

  **Integrations**

  The following must already be connected in Paradime:

  * [**Linear**](/integrations/linear) — the agent calls `get_linear_issue` to fetch linked tickets
  * [**Slack**](/integrations/slack) — the agent posts reviews to `#pr-reviews` via `post_slack_message`
</Info>

<Info>
  Linear is recommended but not strictly required. If the PR description has no Linear link, the agent will note it under SCOPE and continue with the rest of the review.
</Info>

## What You'll Build

By the end of this guide you'll have:

* A DinoAI agent configured to review every pull request end-to-end
* A Python driver script that extracts PR context and triggers the agent
* A GitHub Actions workflow that runs automatically on every PR open or update
* A structured Slack review posted to `#pr-reviews` with a clear APPROVE / REQUEST\_CHANGES / REJECT verdict

### What the Review Looks Like

Once triggered, the agent posts a single structured message to `#pr-reviews` in Slack:

```
SCOPE   — does the PR match the Linear ticket? (PASS / PARTIAL / FAIL)
CODE    — correctness, naming, ref/source usage
TESTS   — coverage on new or changed models
DOCS    — descriptions on model + columns
IMPACT  — downstream models / exposures touched
VERDICT — APPROVE / REQUEST_CHANGES / REJECT
```

The agent always cites the file and line number for every issue it raises. It will never return `APPROVE` if either `TESTS` or `DOCS` is `FAIL`.

<Frame>
  <img src="https://mintcdn.com/paradime-docs/9ZsuN0wtxDqr0yUA/images/image-17.png?fit=max&auto=format&n=9ZsuN0wtxDqr0yUA&q=85&s=202b9954415ff36d2a8181f89fa95969" alt="" width="1526" height="1860" data-path="images/image-17.png" />
</Frame>

## How It Works

When a PR is opened or updated, GitHub Actions runs a Python script that collects the PR title, description, changed files, and any linked Linear issue. That context is handed to the DinoAI agent in a single trigger message. The agent then:

1. Reads every changed `.sql` and `.yml` file
2. Fetches the linked Linear ticket and checks the PR delivers what was asked for
3. Verifies test coverage on new or modified models
4. Checks documentation completeness in schema YAML
5. Flags any breaking-change risk for downstream consumers
6. Posts one structured review to the PR's Slack thread

<Steps>
  <Step title="Create the Agent YAML">
    Create the following file in your repository at `.dinoai/agents/pr-reviewer-e2e.yml`. This defines the agent's role, goal, tools, and Slack output channel.

    ```yaml title=".dinoai/agents/pr-reviewer-e2e.yml" lineNumbers theme={"system"}
    name: pr-reviewer-e2e
    version: 1

    role: >
      Senior Analytics Engineering Reviewer responsible for end-to-end PR review:
      spec alignment, code quality, tests, docs, and downstream impact.

    goal: >
      For the PR referenced in the trigger message:
      (1) read the PR title and description,
      (2) call get_linear_issue to fetch the linked ticket and verify the PR
          delivers what the ticket asked for,
      (3) read every changed .sql and .yml file in the diff,
      (4) check test coverage on new or modified models,
      (5) check documentation completeness in schema YAML,
      (6) flag any breaking-change risk on downstream consumers.
      Post a single structured review to the PR Slack thread under the headings:
      SCOPE / CODE / TESTS / DOCS / IMPACT / VERDICT.

    backstory: >
      You are thorough but never noisy. You always cite the file and line for
      any issue you flag. If the PR description has no Linear link, say so in
      SCOPE and continue. You never approve a PR where TESTS or DOCS is FAIL.

    tools:
      mode: allowlist
      list:
        - read_file
        - search_files_and_directories
        - ripgrep_search
        - get_linear_issue
        - run_sql_query
        - post_slack_message

    slack:
      channel: "#pr-reviews"
    ```

    <Info>
      The `tools.mode: allowlist` setting means the agent can only use the tools explicitly listed. This keeps the agent focused and prevents unintended actions.
    </Info>

    <Info>
      The Slack channel is set to `#pr-reviews` by default. Update the `slack.channel` value if your team uses a different channel before committing this file.
    </Info>
  </Step>

  <Step title="Create the Driver Script">
    Create the Python script at `scripts/pr_review_e2e.py`. This script runs inside GitHub Actions and is responsible for:

    * Reading the PR event payload from GitHub
    * Collecting the list of changed files via `git diff`
    * Extracting any linked Linear issue ID from the PR description
    * Assembling a trigger message and handing it to the DinoAI agent

    ```python title="scripts/pr_review_e2e.py" lineNumbers theme={"system"}
    import os
    import re
    import json
    import subprocess
    import urllib.request

    from paradime import Paradime

    paradime = Paradime(
        api_endpoint=os.environ["PARADIME_API_ENDPOINT"],
        api_key=os.environ["PARADIME_API_KEY"],
        api_secret=os.environ["PARADIME_API_SECRET"],
    )

    # 1. Read the PR event payload that GitHub Actions writes to disk
    with open(os.environ["GITHUB_EVENT_PATH"]) as f:
        event = json.load(f)

    pr = event["pull_request"]
    pr_number = pr["number"]
    pr_title = pr["title"]
    pr_body = pr["body"] or ""
    pr_url = pr["html_url"]
    base_sha = pr["base"]["sha"]
    head_sha = pr["head"]["sha"]

    # 2. Get the list of changed files
    changed = subprocess.check_output(
        ["git", "diff", "--name-only", f"{base_sha}...{head_sha}"]
    ).decode().splitlines()

    # 3. Extract Linear issue id from the PR body (e.g. "Closes DATA-417")
    linear_match = re.search(r"\b([A-Z]{2,10}-\d+)\b", pr_body)
    linear_id = linear_match.group(1) if linear_match else None

    # 4. Build the trigger message
    files_block = "\n".join(f"  - {f}" for f in changed) or "  (no files)"
    linear_block = (
        f"Linked Linear issue: {linear_id}. Call get_linear_issue to read it."
        if linear_id
        else "No Linear issue link found in the PR description."
    )

    message = f"""Review PR #{pr_number}: {pr_title}
    URL: {pr_url}

    PR description:
    \"\"\"
    {pr_body}
    \"\"\"

    {linear_block}

    Changed files:
    {files_block}

    Read each changed file with read_file, verify the PR delivers what the ticket
    asked for, and post your review to the PR Slack thread under SCOPE / CODE /
    TESTS / DOCS / IMPACT / VERDICT.
    """

    result = paradime.dinoai_agents.trigger_run(
        agent="pr-reviewer-e2e",
        message=message,
    )
    print(f"Started review session: {result.agent_session_id}")

    # 5. Post the session id back to the PR as a comment
    gh_token = os.environ.get("GITHUB_TOKEN")
    if gh_token:
        repo = os.environ["GITHUB_REPOSITORY"]
        body = f"DinoAI review started — session `{result.agent_session_id}` (see #pr-reviews)."
        req = urllib.request.Request(
            f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments",
            data=json.dumps({"body": body}).encode(),
            headers={
                "Authorization": f"Bearer {gh_token}",
                "Accept": "application/vnd.github+json",
            },
            method="POST",
        )
        urllib.request.urlopen(req).read()
    ```

    <Info>
      The script posts the DinoAI session ID back to the PR as a comment so your team can track the review. Remove step 5 if you prefer Slack-only updates.
    </Info>
  </Step>

  <Step title="Add Your Paradime Credentials to GitHub Secrets">
    The driver script authenticates with Paradime using three values. Add the following as GitHub Actions secrets in your repository under **Settings → Secrets and variables → Actions**:

    * `PARADIME_API_KEY`
    * `PARADIME_API_SECRET`
    * `PARADIME_API_ENDPOINT`

    <Frame>
      <img src="https://mintcdn.com/paradime-docs/_V48tdxcy1g_Y-dv/images/image-20.png?fit=max&auto=format&n=_V48tdxcy1g_Y-dv&q=85&s=c8f05bc8be1d614692ce5bca5db55010" alt="" width="2710" height="1514" data-path="images/image-20.png" />
    </Frame>
  </Step>

  <Step title="Create the GitHub Actions Workflow">
    Create the workflow file at `.github/workflows/pr-reviewer-e2e.yml`. This triggers the driver script automatically whenever a PR is opened, updated, or marked as ready for review.

    ```yaml title=".github/workflows/pr-reviewer-e2e.yml" lineNumbers theme={"system"}
    name: DinoAI end-to-end PR review

    on:
      pull_request:
        types: [opened, synchronize, ready_for_review]

    permissions:
      pull-requests: write
      contents: read

    jobs:
      review:
        runs-on: ubuntu-latest
        timeout-minutes: 35
        steps:
          - uses: actions/checkout@v4
            with:
              fetch-depth: 0   # need history for git diff between base and head

          - uses: actions/setup-python@v5
            with:
              python-version: "3.11"

          - name: Install Paradime SDK
            run: pip install paradime-io

          - name: Run end-to-end PR reviewer
            env:
              PARADIME_API_ENDPOINT:  ${{ secrets.PARADIME_API_ENDPOINT }}
              PARADIME_API_KEY: ${{ secrets.PARADIME_API_KEY }}
              PARADIME_API_SECRET: ${{ secrets.PARADIME_API_SECRET }}
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            run: python scripts/pr_review_e2e.py
    ```
  </Step>
</Steps>

### Optional: Block the Workflow on a REJECT Verdict

By default the workflow fires and forgets. If you want the GitHub Actions job to fail when the agent rejects the PR, replace `trigger_run` with `trigger_run_and_wait` in the driver script:

```python theme={"system"}
run = paradime.dinoai_agents.trigger_run_and_wait(
    agent="pr-reviewer-e2e",
    message=message,
    timeout=1800,
)

verdict_line = next(
    (line for line in run.messages[-1].content.splitlines() if line.startswith("VERDICT:")),
    "VERDICT: UNKNOWN",
)
print(verdict_line)

if "REJECT" in verdict_line:
    raise SystemExit("PR rejected by DinoAI reviewer")
```

<Info>
  `timeout=1800` allows the agent up to 30 minutes to complete its review. The GitHub Actions workflow has a matching `timeout-minutes: 35` to account for startup overhead.
</Info>

## Related Docs

* [**Programmable Agents — Quick Start** — getting started with DinoAI agents](/products/dino-ai/programmable-agents/quick-start)
* [**Programmable Agents — YAML Configuration** — full reference for agent config options](/products/dino-ai/programmable-agents/yaml-configuration)
* [**Programmable Agents — Tools Reference** — all available tools including `read_file`, `get_linear_issue`, and `post_slack_message`](/products/dino-ai/programmable-agents/tools-reference)
* [**Linear Integration** — connecting Linear to Paradime](/integrations/linear)
* [**Slack Integration** — connecting Slack to Paradime](/integrations/slack)
* [**Paradime API & Credentials** — where to find your API endpoint, key, and secret](/developers/generate-api-keys-legacy)


## Related topics

- [Workspaces and data mesh](/guides/paradime-fundamentals/workspaces-and-data-mesh.md)
- [Triggering from the API](/products/dino-ai/programmable-agents/api-reference.md)
- [God Mode](/products/dino-ai/copilot/god-mode.md)
- [2. Self-Healing](/products/bolt/creating-schedules/2.-self-healing.md)
- [Paradime Docs Tool](/products/dino-ai/tools-and-features/paradime-docs-tool.md)
