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.
- gatehouse_crunchtools-0.5.0/.gemini/config.yaml +6 -0
- gatehouse_crunchtools-0.5.0/.gemini/styleguide.md +27 -0
- gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/bug_report.yml +68 -0
- gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- gatehouse_crunchtools-0.5.0/.github/ISSUE_TEMPLATE/feature_request.yml +49 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/ci.yml +68 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/container.yml +112 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/gatehouse.yml +54 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/gourmand.yml +46 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/publish.yml +29 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/review.yml +134 -0
- gatehouse_crunchtools-0.5.0/.github/workflows/security.yml +144 -0
- gatehouse_crunchtools-0.5.0/.gitignore +10 -0
- gatehouse_crunchtools-0.5.0/.pre-commit-config.yaml +27 -0
- gatehouse_crunchtools-0.5.0/.specify/memory/constitution.md +65 -0
- gatehouse_crunchtools-0.5.0/.specify/specs/001-workflow-secret-exfiltration/plan.md +26 -0
- gatehouse_crunchtools-0.5.0/.specify/specs/001-workflow-secret-exfiltration/spec.md +37 -0
- gatehouse_crunchtools-0.5.0/.specify/specs/002-pr-review-comments/plan.md +36 -0
- gatehouse_crunchtools-0.5.0/.specify/specs/002-pr-review-comments/spec.md +47 -0
- gatehouse_crunchtools-0.5.0/CHANGELOG.md +95 -0
- gatehouse_crunchtools-0.5.0/CLAUDE.md +55 -0
- gatehouse_crunchtools-0.5.0/Containerfile +41 -0
- gatehouse_crunchtools-0.5.0/LICENSE +661 -0
- gatehouse_crunchtools-0.5.0/PKG-INFO +118 -0
- gatehouse_crunchtools-0.5.0/README.md +95 -0
- gatehouse_crunchtools-0.5.0/examples/gatehouse.yml +81 -0
- gatehouse_crunchtools-0.5.0/examples/gourmand.yml +16 -0
- gatehouse_crunchtools-0.5.0/gourmand-exceptions.toml +29 -0
- gatehouse_crunchtools-0.5.0/gourmand.toml +53 -0
- gatehouse_crunchtools-0.5.0/pyproject.toml +73 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/__init__.py +3 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/agents.py +309 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/cli.py +132 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/diffview.py +237 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/gemini.py +129 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/github.py +210 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/output.py +150 -0
- gatehouse_crunchtools-0.5.0/src/gatehouse/review.py +274 -0
- gatehouse_crunchtools-0.5.0/tests/test_agents.py +183 -0
- gatehouse_crunchtools-0.5.0/tests/test_diffview.py +199 -0
- gatehouse_crunchtools-0.5.0/tests/test_fork_safety.py +121 -0
- gatehouse_crunchtools-0.5.0/tests/test_github.py +316 -0
- gatehouse_crunchtools-0.5.0/tests/test_output.py +182 -0
- gatehouse_crunchtools-0.5.0/tests/test_review.py +832 -0
- gatehouse_crunchtools-0.5.0/uv.lock +346 -0
|
@@ -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,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[@]}"
|