gatehouse-crunchtools 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. gatehouse_crunchtools-0.5.0/.gemini/config.yaml +6 -0
  2. gatehouse_crunchtools-0.5.0/.gemini/styleguide.md +27 -0
  3. gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/bug_report.yml +68 -0
  4. gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/feature_request.yml +49 -0
  6. gatehouse_crunchtools-0.5.0/.github/workflows/ci.yml +68 -0
  7. gatehouse_crunchtools-0.5.0/.github/workflows/container.yml +112 -0
  8. gatehouse_crunchtools-0.5.0/.github/workflows/gatehouse.yml +54 -0
  9. gatehouse_crunchtools-0.5.0/.github/workflows/gourmand.yml +46 -0
  10. gatehouse_crunchtools-0.5.0/.github/workflows/publish.yml +29 -0
  11. gatehouse_crunchtools-0.5.0/.github/workflows/review.yml +134 -0
  12. gatehouse_crunchtools-0.5.0/.github/workflows/security.yml +144 -0
  13. gatehouse_crunchtools-0.5.0/.gitignore +10 -0
  14. gatehouse_crunchtools-0.5.0/.pre-commit-config.yaml +27 -0
  15. gatehouse_crunchtools-0.5.0/.specify/memory/constitution.md +65 -0
  16. gatehouse_crunchtools-0.5.0/.specify/specs/001-workflow-secret-exfiltration/plan.md +26 -0
  17. gatehouse_crunchtools-0.5.0/.specify/specs/001-workflow-secret-exfiltration/spec.md +37 -0
  18. gatehouse_crunchtools-0.5.0/.specify/specs/002-pr-review-comments/plan.md +36 -0
  19. gatehouse_crunchtools-0.5.0/.specify/specs/002-pr-review-comments/spec.md +47 -0
  20. gatehouse_crunchtools-0.5.0/CHANGELOG.md +95 -0
  21. gatehouse_crunchtools-0.5.0/CLAUDE.md +55 -0
  22. gatehouse_crunchtools-0.5.0/Containerfile +41 -0
  23. gatehouse_crunchtools-0.5.0/LICENSE +661 -0
  24. gatehouse_crunchtools-0.5.0/PKG-INFO +118 -0
  25. gatehouse_crunchtools-0.5.0/README.md +95 -0
  26. gatehouse_crunchtools-0.5.0/examples/gatehouse.yml +81 -0
  27. gatehouse_crunchtools-0.5.0/examples/gourmand.yml +16 -0
  28. gatehouse_crunchtools-0.5.0/gourmand-exceptions.toml +29 -0
  29. gatehouse_crunchtools-0.5.0/gourmand.toml +53 -0
  30. gatehouse_crunchtools-0.5.0/pyproject.toml +73 -0
  31. gatehouse_crunchtools-0.5.0/src/gatehouse/__init__.py +3 -0
  32. gatehouse_crunchtools-0.5.0/src/gatehouse/agents.py +309 -0
  33. gatehouse_crunchtools-0.5.0/src/gatehouse/cli.py +132 -0
  34. gatehouse_crunchtools-0.5.0/src/gatehouse/diffview.py +237 -0
  35. gatehouse_crunchtools-0.5.0/src/gatehouse/gemini.py +129 -0
  36. gatehouse_crunchtools-0.5.0/src/gatehouse/github.py +210 -0
  37. gatehouse_crunchtools-0.5.0/src/gatehouse/output.py +150 -0
  38. gatehouse_crunchtools-0.5.0/src/gatehouse/review.py +274 -0
  39. gatehouse_crunchtools-0.5.0/tests/test_agents.py +183 -0
  40. gatehouse_crunchtools-0.5.0/tests/test_diffview.py +199 -0
  41. gatehouse_crunchtools-0.5.0/tests/test_fork_safety.py +121 -0
  42. gatehouse_crunchtools-0.5.0/tests/test_github.py +316 -0
  43. gatehouse_crunchtools-0.5.0/tests/test_output.py +182 -0
  44. gatehouse_crunchtools-0.5.0/tests/test_review.py +832 -0
  45. gatehouse_crunchtools-0.5.0/uv.lock +346 -0
@@ -0,0 +1,6 @@
1
+ code_review:
2
+ ignore:
3
+ - "vendor/**"
4
+ - "*.min.js"
5
+ - ".gourmand-cache/**"
6
+ - "*.lock"
@@ -0,0 +1,27 @@
1
+ # Gatehouse Style Guide
2
+
3
+ ## Architecture
4
+
5
+ Gatehouse is a Python CLI tool with 5 concurrent Gemini agents for code review.
6
+
7
+ ### Module Structure
8
+ - `cli.py` -- argparse entry point
9
+ - `agents.py` -- agent prompt definitions (frozen dataclasses)
10
+ - `diffview.py` -- unified diff to structured BEFORE/AFTER view for agents
11
+ - `gemini.py` -- httpx async client for Gemini REST API
12
+ - `review.py` -- orchestration (git diff, concurrent agents, exit codes)
13
+ - `output.py` -- formatted terminal output with ANSI colors
14
+
15
+ ### Conventions
16
+ - Python 3.11+, strict mypy, ruff linting
17
+ - httpx for HTTP (no vendor SDKs)
18
+ - argparse (stdlib) for CLI (no click/typer)
19
+ - All API calls mocked in tests (no live calls in CI)
20
+ - Exit 0 = success/advisory, Exit 1 = blocking findings, Exit 2 = usage error
21
+ - Respect NO_COLOR environment variable
22
+ - Structured output to stdout, diagnostics to stderr
23
+
24
+ ### What NOT to Flag
25
+ - The agent prompt strings are intentionally long -- do not suggest splitting them
26
+ - The `dict[str, Any]` types for JSON findings are intentional -- no Pydantic models needed here
27
+ - subprocess calls to git are safe and expected
@@ -0,0 +1,68 @@
1
+ name: Bug Report
2
+ description: Report a bug or unexpected behavior
3
+ title: "[Bug]: "
4
+ labels: ["bug", "triage"]
5
+ body:
6
+ - type: textarea
7
+ id: description
8
+ attributes:
9
+ label: Bug Description
10
+ description: A clear description of the bug
11
+ placeholder: What happened?
12
+ validations:
13
+ required: true
14
+
15
+ - type: textarea
16
+ id: steps
17
+ attributes:
18
+ label: Steps to Reproduce
19
+ description: How can we reproduce this bug?
20
+ placeholder: |
21
+ 1. Run `gatehouse --base main`
22
+ 2. Observe error '...'
23
+ validations:
24
+ required: true
25
+
26
+ - type: textarea
27
+ id: expected
28
+ attributes:
29
+ label: Expected Behavior
30
+ description: What did you expect to happen?
31
+ validations:
32
+ required: true
33
+
34
+ - type: textarea
35
+ id: actual
36
+ attributes:
37
+ label: Actual Behavior
38
+ description: What actually happened? Include error messages if available.
39
+ validations:
40
+ required: true
41
+
42
+ - type: dropdown
43
+ id: install_method
44
+ attributes:
45
+ label: Installation Method
46
+ description: How are you running gatehouse?
47
+ options:
48
+ - uv tool install
49
+ - pipx install
50
+ - Container (podman run)
51
+ - From source (uv sync)
52
+ validations:
53
+ required: true
54
+
55
+ - type: input
56
+ id: version
57
+ attributes:
58
+ label: Version
59
+ description: Output of `gatehouse --help` header or check pyproject.toml
60
+ placeholder: "0.1.0"
61
+ validations:
62
+ required: true
63
+
64
+ - type: textarea
65
+ id: additional
66
+ attributes:
67
+ label: Additional Context
68
+ description: Any other relevant information (logs, git state, environment)
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Gemini API Documentation
4
+ url: https://ai.google.dev/gemini-api/docs
5
+ about: Check the Google Gemini API docs for API behavior questions
@@ -0,0 +1,49 @@
1
+ name: Feature Request
2
+ description: Suggest a new feature or enhancement
3
+ title: "[Feature]: "
4
+ labels: ["enhancement", "triage"]
5
+ body:
6
+ - type: textarea
7
+ id: problem
8
+ attributes:
9
+ label: Problem Statement
10
+ description: What problem does this feature solve?
11
+ placeholder: I need to...
12
+ validations:
13
+ required: true
14
+
15
+ - type: textarea
16
+ id: solution
17
+ attributes:
18
+ label: Proposed Solution
19
+ description: Describe the feature or change you'd like
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ id: alternatives
25
+ attributes:
26
+ label: Alternatives Considered
27
+ description: What alternatives have you considered?
28
+
29
+ - type: dropdown
30
+ id: scope
31
+ attributes:
32
+ label: Area
33
+ description: Which area does this feature belong to?
34
+ options:
35
+ - Agent prompts (bug-hunter, security-scan, etc.)
36
+ - Output formatting
37
+ - CLI flags / interface
38
+ - API client (Gemini integration)
39
+ - New agent
40
+ - CI / integration
41
+ - Other
42
+ validations:
43
+ required: true
44
+
45
+ - type: textarea
46
+ id: additional
47
+ attributes:
48
+ label: Additional Context
49
+ description: Any other relevant information or examples
@@ -0,0 +1,68 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+ with:
22
+ version: "latest"
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --all-extras
29
+
30
+ - name: Run linter
31
+ run: uv run ruff check src tests
32
+
33
+ - name: Run type checker
34
+ run: uv run mypy src
35
+
36
+ - name: Run tests
37
+ run: uv run pytest -v
38
+
39
+ # Gatehouse code review runs in its own workflow (gatehouse.yml) on
40
+ # pull_request_target, so it covers fork PRs too. See that file.
41
+
42
+ # Call our own reusable workflow rather than inlining the job. Gatehouse
43
+ # hosts gourmand.yml for the whole fleet; keeping a private copy here is the
44
+ # copy-paste drift RT #1468 was filed about.
45
+ gourmand-review:
46
+ uses: ./.github/workflows/gourmand.yml
47
+
48
+ build-container:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+
53
+ - name: Build container image
54
+ run: docker build -f Containerfile -t gatehouse:test .
55
+
56
+ validate-constitution:
57
+ name: Constitution Validation
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+
62
+ - uses: actions/checkout@v4
63
+ with:
64
+ repository: crunchtools/constitution
65
+ path: .constitution
66
+
67
+ - name: Validate constitution
68
+ run: python3 .constitution/validate-constitution.py .specify/memory/constitution.md --verbose
@@ -0,0 +1,112 @@
1
+ name: Container Build & Push
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ branches: [master]
9
+ workflow_dispatch:
10
+
11
+ env:
12
+ QUAY_IMAGE: quay.io/crunchtools/gatehouse
13
+ GHCR_IMAGE: ghcr.io/crunchtools/gatehouse
14
+
15
+ jobs:
16
+ build-and-push-quay:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+
21
+ steps:
22
+ - name: Checkout repository
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Set up Docker Buildx
26
+ uses: docker/setup-buildx-action@v3
27
+
28
+ - name: Log in to Quay.io
29
+ if: github.event_name != 'pull_request'
30
+ uses: docker/login-action@v3
31
+ with:
32
+ registry: quay.io
33
+ username: ${{ secrets.QUAY_USERNAME }}
34
+ password: ${{ secrets.QUAY_PASSWORD }}
35
+
36
+ - name: Extract metadata
37
+ id: meta
38
+ uses: docker/metadata-action@v5
39
+ with:
40
+ images: ${{ env.QUAY_IMAGE }}
41
+ tags: |
42
+ type=ref,event=branch
43
+ type=ref,event=pr
44
+ type=semver,pattern={{version}}
45
+ type=semver,pattern={{major}}.{{minor}}
46
+ type=raw,value=latest,enable={{is_default_branch}}
47
+
48
+ - name: Build and push
49
+ uses: docker/build-push-action@v6
50
+ with:
51
+ context: .
52
+ file: ./Containerfile
53
+ push: ${{ github.event_name != 'pull_request' }}
54
+ tags: ${{ steps.meta.outputs.tags }}
55
+ labels: ${{ steps.meta.outputs.labels }}
56
+ cache-from: type=gha
57
+ cache-to: type=gha,mode=max
58
+
59
+ - name: Run Trivy vulnerability scanner
60
+ continue-on-error: true
61
+ if: github.event_name != 'pull_request'
62
+ uses: aquasecurity/trivy-action@master
63
+ with:
64
+ image-ref: ${{ env.QUAY_IMAGE }}:latest
65
+ format: "table"
66
+ exit-code: "0"
67
+ severity: "CRITICAL,HIGH"
68
+
69
+ build-and-push-ghcr:
70
+ runs-on: ubuntu-latest
71
+ needs: build-and-push-quay
72
+ if: github.event_name != 'pull_request'
73
+ permissions:
74
+ contents: read
75
+ packages: write
76
+
77
+ steps:
78
+ - name: Checkout repository
79
+ uses: actions/checkout@v4
80
+
81
+ - name: Set up Docker Buildx
82
+ uses: docker/setup-buildx-action@v3
83
+
84
+ - name: Log in to GitHub Container Registry
85
+ uses: docker/login-action@v3
86
+ with:
87
+ registry: ghcr.io
88
+ username: ${{ github.repository_owner }}
89
+ password: ${{ github.token }}
90
+
91
+ - name: Extract metadata
92
+ id: meta
93
+ uses: docker/metadata-action@v5
94
+ with:
95
+ images: ${{ env.GHCR_IMAGE }}
96
+ tags: |
97
+ type=ref,event=branch
98
+ type=ref,event=pr
99
+ type=semver,pattern={{version}}
100
+ type=semver,pattern={{major}}.{{minor}}
101
+ type=raw,value=latest,enable={{is_default_branch}}
102
+
103
+ - name: Build and push
104
+ uses: docker/build-push-action@v6
105
+ with:
106
+ context: .
107
+ file: ./Containerfile
108
+ push: true
109
+ tags: ${{ steps.meta.outputs.tags }}
110
+ labels: ${{ steps.meta.outputs.labels }}
111
+ cache-from: type=gha
112
+ cache-to: type=gha,mode=max
@@ -0,0 +1,54 @@
1
+ # Gatehouse dogfoods its own fork-safe review on EVERY PR — internal or external.
2
+ #
3
+ # This is the exact pattern shipped to adopters in examples/gatehouse.yml, except
4
+ # it references the reusable workflow locally (./.github/workflows/review.yml)
5
+ # instead of by tag. pull_request_target runs the trusted base-branch version of
6
+ # these files, so a PR can neither disable the guard nor tamper with the reviewer.
7
+
8
+ name: Gatehouse
9
+
10
+ on:
11
+ pull_request_target:
12
+ branches: [master]
13
+ types: [opened, synchronize, reopened]
14
+
15
+ permissions: {} # default-deny; each job grants only what it needs
16
+
17
+ jobs:
18
+ # 1) Reject PRs from outside contributors that modify workflow files.
19
+ guard:
20
+ name: Protect workflows
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ contents: read
24
+ pull-requests: write
25
+ steps:
26
+ - name: Reject workflow changes from untrusted PRs
27
+ env:
28
+ GH_TOKEN: ${{ github.token }}
29
+ PR: ${{ github.event.pull_request.number }}
30
+ AUTHOR_ASSOC: ${{ github.event.pull_request.author_association }}
31
+ run: |
32
+ set -euo pipefail
33
+ case "$AUTHOR_ASSOC" in
34
+ OWNER|MEMBER|COLLABORATOR)
35
+ echo "Author '$AUTHOR_ASSOC' is trusted; allowing."; exit 0 ;;
36
+ esac
37
+ changed="$(gh pr diff "$PR" --repo "$GITHUB_REPOSITORY" --name-only)"
38
+ if echo "$changed" | grep -qE '^\.github/workflows/'; then
39
+ echo "::error::PR modifies .github/workflows/ — not accepted from external contributors."
40
+ gh pr comment "$PR" --repo "$GITHUB_REPOSITORY" --body \
41
+ $'🚫 **Workflow changes are not accepted via pull request.** Please open an issue and a maintainer will make the change.'
42
+ exit 1
43
+ fi
44
+ echo "No workflow files changed."
45
+
46
+ # 2) Review the PR diff (no checkout, BYO key) via the reusable workflow.
47
+ review:
48
+ name: Gatehouse review
49
+ permissions:
50
+ contents: read
51
+ pull-requests: write
52
+ uses: ./.github/workflows/review.yml
53
+ secrets:
54
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
@@ -0,0 +1,46 @@
1
+ # Gourmand — AI slop detection (reusable workflow)
2
+ #
3
+ # Runs the pinned Gourmand container against the caller repo. Centralizing
4
+ # the job body here means a fix or image change reaches every adopter on
5
+ # their next CI run instead of sitting frozen in a copy-pasted snapshot.
6
+ #
7
+ # Filed per RT #1468: a dead `cargo install --git codeberg.org/mattdm/gourmand`
8
+ # gate kept regenerating fleet-wide because the old `examples/gourmand.yml`
9
+ # was a copy-paste snippet, not a `workflow_call` reference — fixing it in one
10
+ # repo never propagated to the repos that had already copied it. Do not go
11
+ # back to a copy-paste example; that is the exact bug this workflow fixes.
12
+ #
13
+ # ── How adopters use it ──────────────────────────────────────────────────────
14
+ # Add this job to your CI workflow:
15
+ #
16
+ # jobs:
17
+ # gourmand:
18
+ # name: Code Quality (Gourmand)
19
+ # uses: crunchtools/gatehouse/.github/workflows/gourmand.yml@v0.5.0
20
+
21
+ name: Gourmand (reusable)
22
+
23
+ on:
24
+ workflow_call:
25
+ inputs:
26
+ path:
27
+ description: "Path to scan (default: repo root)"
28
+ type: string
29
+ required: false
30
+ default: "."
31
+ image:
32
+ description: "Gourmand image ref. Override to pin a specific digest/tag."
33
+ type: string
34
+ required: false
35
+ default: "quay.io/crunchtools/gourmand:latest"
36
+
37
+ jobs:
38
+ gourmand:
39
+ name: Code Quality (Gourmand)
40
+ runs-on: ubuntu-latest
41
+ container:
42
+ image: ${{ inputs.image }}
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ - name: Run Gourmand
46
+ run: gourmand --full ${{ inputs.path }}
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v4
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Set up Python
23
+ run: uv python install 3.12
24
+
25
+ - name: Build package
26
+ run: uv build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,134 @@
1
+ # Gatehouse — fork-safe PR review (reusable workflow)
2
+ #
3
+ # Hardened `pull_request_target` review: diff-piped, NO checkout, scoped-secret-bound.
4
+ # Reviews fork PRs with a bring-your-own Gemini key and zero hosting. Safe by
5
+ # construction: the fork's code is only ever read as a unified DIFF on stdin —
6
+ # never checked out to disk, never executed. The container runs `gatehouse`,
7
+ # which is diff-in -> Gemini -> PR-review-comments-out.
8
+ #
9
+ # ── How adopters use it ──────────────────────────────────────────────────────
10
+ # Add `.github/workflows/gatehouse.yml` to your repository:
11
+ #
12
+ # name: Gatehouse Review
13
+ # on:
14
+ # pull_request_target:
15
+ # types: [opened, synchronize, reopened]
16
+ # permissions: {} # default-deny at the top level
17
+ # jobs:
18
+ # review:
19
+ # permissions: # grant ONLY what the review needs
20
+ # contents: read
21
+ # pull-requests: write
22
+ # uses: crunchtools/gatehouse/.github/workflows/review.yml@v1
23
+ # secrets:
24
+ # GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
25
+ #
26
+ # ── Scoped secret (recommended) ──────────────────────────────────────────────
27
+ # Configure GEMINI_API_KEY as a *scoped* secret bound to this trusted reusable
28
+ # workflow (Settings -> Secrets and variables -> Actions -> scope to
29
+ # `crunchtools/gatehouse/.github/workflows/review.yml`). Then no other job in the
30
+ # caller's repo can read the key — closing the "extra job under pull_request_target"
31
+ # footgun. (GitHub 2026 Actions roadmap; verify availability for your plan.)
32
+ #
33
+ # ── Why this is safe under pull_request_target ───────────────────────────────
34
+ # 1. No `actions/checkout` of the fork head — nothing on disk to execute.
35
+ # (Also sidesteps the actions/checkout v7 fork-head block entirely.)
36
+ # 2. The diff arrives as data on stdin; `gatehouse --stdin` never runs `git`
37
+ # against, builds, installs, or executes the PR.
38
+ # 3. The image is pinned to a release tag; the run step invokes only the container
39
+ # entrypoint — no repo-defined tooling runs.
40
+ # 4. Least-privilege GITHUB_TOKEN: contents:read + pull-requests:write.
41
+ # -----------------------------------------------------------------------------
42
+
43
+ name: Gatehouse Review (reusable)
44
+
45
+ on:
46
+ workflow_call:
47
+ inputs:
48
+ model:
49
+ description: "Gemini model override (default: gatehouse built-in)"
50
+ type: string
51
+ required: false
52
+ default: ""
53
+ agents:
54
+ description: "Comma-separated agent slugs (default: all agents)"
55
+ type: string
56
+ required: false
57
+ default: ""
58
+ blocking:
59
+ # Advisory by default: the review posts findings as PR comments but the
60
+ # check ALWAYS passes (exit 0), so a non-deterministic LLM finding can
61
+ # never block a merge. Opt in with `blocking: true` only if you want
62
+ # critical/high findings from blocking agents to fail the check. Even
63
+ # then, do NOT mark this a *required* status check — see examples/.
64
+ description: "Fail the check on blocking findings (default: advisory)."
65
+ type: boolean
66
+ required: false
67
+ default: false
68
+ image:
69
+ # Pinned to the immutable release TAG, not a digest: our registry prunes
70
+ # untagged manifests on rebuild, so a digest pin dies the next time the
71
+ # tag is rebuilt. The release tag is controlled and not rebuilt casually.
72
+ description: "Gatehouse release image tag. Bump per release."
73
+ type: string
74
+ required: false
75
+ default: "quay.io/crunchtools/gatehouse:0.4.0"
76
+ secrets:
77
+ GEMINI_API_KEY:
78
+ required: true
79
+
80
+ jobs:
81
+ review:
82
+ name: Gatehouse Code Review
83
+ runs-on: ubuntu-latest
84
+ permissions:
85
+ contents: read # read PR metadata / diff via the API
86
+ pull-requests: write # post the review (REQUEST_CHANGES / COMMENT)
87
+ steps:
88
+ - name: Review fork diff (no checkout, never executes PR code)
89
+ env:
90
+ GH_TOKEN: ${{ github.token }}
91
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
92
+ PR_NUMBER: ${{ github.event.pull_request.number }}
93
+ # Trusted base repo + base-branch SHA — styleguide/constitution are
94
+ # loaded from HERE via the API, never from the fork head.
95
+ BASE_REF: ${{ github.event.pull_request.base.sha }}
96
+ IMAGE: ${{ inputs.image }}
97
+ MODEL: ${{ inputs.model }}
98
+ AGENTS: ${{ inputs.agents }}
99
+ BLOCKING: ${{ inputs.blocking }}
100
+ run: |
101
+ set -euo pipefail
102
+
103
+ # This reusable workflow only makes sense under pull_request_target,
104
+ # where a fork PR's GITHUB_TOKEN/secrets are available in the BASE
105
+ # repo's trusted context. Fail loudly if there is no PR context.
106
+ if [ -z "${PR_NUMBER:-}" ]; then
107
+ echo "::error::No pull_request context. Call this from 'on: pull_request_target'." >&2
108
+ exit 1
109
+ fi
110
+
111
+ # Optional passthrough flags (only added when provided).
112
+ # Advisory by default: unless the caller sets `blocking: true`, add
113
+ # --advisory so gatehouse exits 0 regardless of findings. The review
114
+ # is still posted; it just never fails the check.
115
+ args=( --stdin --comment )
116
+ [ "$BLOCKING" = "true" ] || args+=( --advisory )
117
+ [ -n "$MODEL" ] && args+=( --model "$MODEL" )
118
+ [ -n "$AGENTS" ] && args+=( --agents "$AGENTS" )
119
+
120
+ # 1) Fetch the PR diff over the API — data only, no fork code on disk.
121
+ # 2) Pipe it into the release-tag-pinned container's `gatehouse --stdin --comment`.
122
+ # - GITHUB_REF is synthesized as refs/pull/N/merge so gatehouse's
123
+ # detect_pr_context() resolves (repo, PR) without mounting the event file.
124
+ # - GITHUB_TOKEN authorizes gatehouse to POST the PR review via the REST API.
125
+ gh pr diff "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --patch \
126
+ | docker run --rm -i \
127
+ -e GEMINI_API_KEY \
128
+ -e GITHUB_TOKEN="$GH_TOKEN" \
129
+ -e GITHUB_REPOSITORY="$GITHUB_REPOSITORY" \
130
+ -e GITHUB_REF="refs/pull/${PR_NUMBER}/merge" \
131
+ -e GATEHOUSE_CONTEXT_REPO="$GITHUB_REPOSITORY" \
132
+ -e GATEHOUSE_CONTEXT_REF="$BASE_REF" \
133
+ "$IMAGE" \
134
+ "${args[@]}"