Skip to content

Commit b08bdbd

Browse files
tylergannonclaude
andcommitted
Initial qa-cli: local runner for pagerguild QA agents
Bootstrap with: uv run https://raw.githubusercontent.com/pagerguild/qa/main/qa.py --target URL --here - qa.py: PEP 723 stdlib-only wrapper around `act`. Resolves a target repo (--here / --path / --repo), mints a 1-hour Doppler service token, overlays qa-team's scripts/.github onto the target's .qa/ tasks, rewrites localhost URLs to host.docker.internal, and runs the qa-matrix.yml workflow with the pre-baked runner image. - Dockerfile: catthehacker/ubuntu:act-latest + Playwright chromium baked in; eliminates the 3-min install on every act run. - build-image.yml: multi-arch (amd64+arm64) push to ghcr.io/pagerguild/qa-runner:latest on push to main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 parents  commit b08bdbd

5 files changed

Lines changed: 557 additions & 0 deletions

File tree

.github/workflows/build-image.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Builds and publishes the pre-baked qa-runner image to GHCR.
2+
#
3+
# Output: ghcr.io/pagerguild/qa-runner:latest (multi-arch: amd64 + arm64).
4+
#
5+
# qa.py pulls this on first run (and treats it as a cache miss on later
6+
# runs once it's tagged locally as pagerguild/qa-runner:local).
7+
8+
name: Build runner image
9+
10+
on:
11+
push:
12+
branches: [main]
13+
paths:
14+
- Dockerfile
15+
- .github/workflows/build-image.yml
16+
workflow_dispatch:
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
packages: write
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- uses: docker/setup-qemu-action@v3
28+
- uses: docker/setup-buildx-action@v3
29+
30+
- uses: docker/login-action@v3
31+
with:
32+
registry: ghcr.io
33+
username: ${{ github.actor }}
34+
password: ${{ secrets.GITHUB_TOKEN }}
35+
36+
- uses: docker/build-push-action@v6
37+
with:
38+
context: .
39+
platforms: linux/amd64,linux/arm64
40+
push: true
41+
tags: ghcr.io/pagerguild/qa-runner:latest
42+
cache-from: type=gha
43+
cache-to: type=gha,mode=max

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__pycache__/
2+
*.pyc
3+
.DS_Store
4+
.secrets

Dockerfile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Pre-baked runner image for `qa.py`. Inherits catthehacker (the standard
2+
# act runner image — has node/npm/python/git/jq/sudo all configured the way
3+
# act expects), then bakes Playwright + chromium on top so we don't pay
4+
# the 3-minute install cost on every local act run.
5+
#
6+
# Built multi-arch (amd64 + arm64) by .github/workflows/build-image.yml in
7+
# this repo, published to ghcr.io/pagerguild/qa-cli/runner:latest. The wrapper
8+
# pins to a digest, not :latest, so cache-busting is explicit.
9+
#
10+
# Local override during dev: `docker build -t qa-cli/runner:local .` and pass
11+
# `--runner-image qa-cli/runner:local` to qa.py.
12+
13+
FROM catthehacker/ubuntu:act-latest
14+
15+
# Auto-link the published GHCR package to this repo so visibility flips
16+
# (private → public) appear in pagerguild/qa's package UI.
17+
LABEL org.opencontainers.image.source=https://github.com/pagerguild/qa
18+
19+
# Pin Playwright to a known-good version. Bump in lockstep with whatever the
20+
# personas expect; the chromium binary version is tied to the Playwright
21+
# release. Re-bake the image when this changes.
22+
ARG PLAYWRIGHT_VERSION=1.49.1
23+
24+
RUN set -eux; \
25+
npx -y playwright@${PLAYWRIGHT_VERSION} install --with-deps chromium; \
26+
CHROME_BIN=$(find /root/.cache/ms-playwright -path '*/chromium-*/chrome-linux*/chrome' -type f | head -1); \
27+
test -n "$CHROME_BIN"; \
28+
ln -sf "$CHROME_BIN" /usr/bin/chromium; \
29+
test -x /usr/bin/chromium; \
30+
echo "baked $CHROME_BIN → /usr/bin/chromium"
31+
32+
# Reset to the entrypoint catthehacker expects so act's container lifecycle
33+
# (tail -f /dev/null then docker exec) keeps working.
34+
ENTRYPOINT []
35+
CMD ["bash"]

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# qa
2+
3+
Local runner for pagerguild QA agents.
4+
5+
```sh
6+
uv run https://raw.githubusercontent.com/pagerguild/qa/main/qa.py \
7+
--target http://localhost:5173 --here
8+
```
9+
10+
Runs every `.qa/<agent>/` task in your repo against any URL, in
11+
parallel matrix containers, using the same workflow that drives CI.
12+
Output streams into the team's Supabase reader at
13+
https://qa.guys.dev.guilde.ai.
14+
15+
## Prereqs
16+
17+
- `uv``brew install uv`
18+
- `gh``brew install gh && gh auth login`
19+
- `doppler``brew install dopplerhq/cli/doppler`, `doppler login`,
20+
and `doppler setup` from a directory scoped to a Doppler config that
21+
has the QA agent's secrets
22+
- `act``brew install act`
23+
- Docker Desktop, running
24+
25+
## Usage
26+
27+
```
28+
qa --target URL (--here | --path DIR | --repo OWNER/NAME [--branch B])
29+
[--qa-dir DIR] [--verbose]
30+
```
31+
32+
- `--here` runs against the current directory's checkout.
33+
- `--path DIR` runs against an existing checkout at `DIR`.
34+
- `--repo OWNER/NAME` clones the repo fresh into a temp dir and uses
35+
that. Add `--branch` to pin a branch.
36+
37+
`localhost` URLs get rewritten to `host.docker.internal` automatically
38+
so the agent inside the container can reach your dev server.
39+
40+
On Apple Silicon, native arm64 containers are selected automatically.

0 commit comments

Comments
 (0)