Files
gadfly/.gitea/workflows/review-reusable.yml
T
Steve Dudenhoeffer 4cef1db686
Build & push image / build-and-push (pull_request) Successful in 8s
Adversarial Review (Gadfly) / review (pull_request) Successful in 5m31s
fix: use un-hyphenated 'llamaswap' provider for the ragnaros endpoint
PR #13's self-review showed the pinned image (sha-c342bdb) rejects the
'llama-swaps' provider spelling in GADFLY_ENDPOINT (its endpointProvider only
accepts 'llamaswap'; the hyphenated aliases were added to the binary later).
Switch GADFLY_ENDPOINT_RAGNAROS to llamaswap|https://... so the 4090 Ti
endpoint registers and ragnaros/qwen3.6-27b resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 00:37:49 -04:00

146 lines
9.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Gadfly — REUSABLE adversarial-review workflow (Gitea `workflow_call`).
#
# Centralizes the ~90-line consumer stub so a repo can subscribe to Gadfly with
# a tiny caller. A consumer workflow does:
#
# jobs:
# review:
# if: ... # actor gate for the comment trigger
# uses: steve/gadfly/.gitea/workflows/review-reusable.yml@v1
# secrets: # forward ONLY what the reviewer needs
# OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }}
# CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# with: { allowed_users: "..." } # config inputs are optional (see below)
#
# Inputs ship the DEFAULT swarm (see the inputs block): 3 cloud models + the
# Claude Code engine + a local 4090 Ti (qwen3.6-27b via llama-swap), 5-lens suite
# (3 claude models concurrent / 5 lenses each; the 4090 Ti runs 1 model × 1 lens). A consumer
# inherits it by omitting `with:` entirely, or overrides any field (e.g.
# `models:` for a cloud-only / different-provider setup; "" falls back to the
# image's built-in default). Secrets are DECLARED below (workflow_call.secrets) so a
# caller forwards only the credentials the reviewer actually uses — least
# privilege — rather than `secrets: inherit`, which leaks every caller secret
# (registry/deploy/db creds) into this workflow. `secrets: inherit` still works
# if you accept that exposure; the explicit form is recommended. GITEA_TOKEN is
# the automatic job token (no need to forward it).
#
# Advisory only — never blocks a merge. The image is pinned to an immutable
# :sha- tag here (act_runner caches :latest); bump it per Gadfly release.
# Consumers should pin `uses: ...@v1` — a curated release tag moved on deliberate
# releases, so central tuning here propagates without per-consumer edits — or a
# full `@<sha>` for an immutable pin. Avoid `@main` (moves on every push).
name: Gadfly review (reusable)
on:
workflow_call:
# Inputs ship the DEFAULT Gadfly swarm so a consumer can just call this
# workflow (no `with:` block) and inherit it. The default is opinionated —
# 3 strong cloud models + the Claude Code engine (sonnet/opus/opus:max) + a
# local 4090 Ti (qwen3.6-27b via llama-swap at GADFLY_ENDPOINT_RAGNAROS), the
# 5-lens suite, with all 3 claude models concurrent and each running its 5
# lenses at once (the 4090 Ti runs 1 model × 1 lens — a single local GPU). It
# needs OLLAMA_CLOUD_API_KEY and CLAUDE_CODE_OAUTH_TOKEN; a consumer
# with only one (or a different provider) overrides `models:` (and forwards
# just the secrets it uses). Set any input to "" to fall back to the
# image/entrypoint built-in default.
#
# Peak claude concurrency = provider_concurrency × provider_lens_concurrency
# (3 models × 5 lenses = up to 15 concurrent `claude -p` per pass). If you hit
# subscription rate limits or runner load, dial claude-code down in either knob.
inputs:
models: { type: string, default: "minimax-m3:cloud,glm-5.2:cloud,deepseek-v4-pro:cloud,claude-code/sonnet,claude-code/opus,claude-code/opus:max,ragnaros/qwen3.6-27b" } # GADFLY_MODELS (csv); ragnaros/* = the 4090 Ti via llama-swap (see GADFLY_ENDPOINT_RAGNAROS)
specialists: { type: string, default: "security,correctness,maintainability,performance,error-handling" } # GADFLY_SPECIALISTS (5-lens default suite)
provider: { type: string, default: "" } # GADFLY_PROVIDER
base_url: { type: string, default: "" } # GADFLY_BASE_URL
provider_concurrency: { type: string, default: "ollama-cloud=3,claude-code=3,ragnaros=1" } # GADFLY_PROVIDER_CONCURRENCY (claude all 3 at once; ragnaros 4090 Ti one model at a time)
provider_lens_concurrency: { type: string, default: "ollama-cloud=3,claude-code=5,ragnaros=1" } # GADFLY_PROVIDER_LENS_CONCURRENCY (claude 5 lenses at once; ragnaros 1 lens at a time)
timeout_secs: { type: string, default: "600" } # GADFLY_TIMEOUT_SECS (per lens)
max_steps: { type: string, default: "14" } # GADFLY_MAX_STEPS
worker_model: { type: string, default: "" } # GADFLY_WORKER_MODEL
allowed_users: { type: string, default: "" } # GADFLY_ALLOWED_USERS (consumer-specific; set in your stub)
trigger_phrase: { type: string, default: "" } # GADFLY_TRIGGER_PHRASE
# Job wall-clock cap. 90 as a default: the 5-lens suite across a slow lane
# (claude-code with extended thinking) over two passes can run long.
timeout_minutes: { type: number, default: 90 }
# Declared so callers can forward ONLY the secrets the reviewer needs
# (least privilege) instead of `secrets: inherit`, which would hand this
# workflow every secret in the caller's repo (registry/deploy/db creds the
# review never touches). All optional — an unset/unpassed secret resolves to
# empty, harmless for the providers a given consumer doesn't use. GITEA_TOKEN
# is the automatic job token and need not be declared/forwarded. Consumers
# with bespoke GADFLY_ENDPOINT_<NAME>s beyond M1/M5 need the full stub.
secrets:
OLLAMA_CLOUD_API_KEY: { required: false }
OPENAI_API_KEY: { required: false }
ANTHROPIC_API_KEY: { required: false }
GOOGLE_API_KEY: { required: false }
GADFLY_API_KEY: { required: false }
CLAUDE_CODE_OAUTH_TOKEN: { required: false }
GADFLY_ENDPOINT_M1: { required: false }
GADFLY_ENDPOINT_M5: { required: false }
GADFLY_FINDINGS_URL: { required: false }
GADFLY_FINDINGS_TOKEN: { required: false }
# The reusable job posts the review comment, so it needs issues/PR write. Gitea
# caps these by the caller's granted permissions; declaring them here is explicit.
permissions:
contents: read
issues: write
pull-requests: write
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.timeout_minutes }}
steps:
- uses: docker://gitea.stevedudenhoeffer.com/steve/gadfly:sha-c342bdb
env:
# --- event context (from the CALLER's github.*) -------------------
GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
# github.token is the auto job token from the github CONTEXT (not a
# secret), so it's present even without `secrets: inherit`. Using
# secrets.GITEA_TOKEN here would be empty under explicit secret
# forwarding, since the auto token isn't a forwarded workflow_call secret.
GITEA_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
PR: ${{ github.event.pull_request.number || github.event.issue.number || github.event.inputs.pr_number }}
PR_BRANCH: ${{ github.head_ref }}
IS_DRAFT: ${{ github.event.pull_request.draft }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_ID: ${{ github.event.comment.id }}
ACTOR: ${{ github.actor }}
# --- provider auth (forwarded workflow_call secrets; empty if the caller doesn't forward it) -
OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GADFLY_API_KEY: ${{ secrets.GADFLY_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Common named foreman/LAN endpoints (optional). Consumers with other
# GADFLY_ENDPOINT_<NAME>s need the full stub (examples/), since a
# reusable workflow can't enumerate arbitrary names.
GADFLY_ENDPOINT_M1: ${{ secrets.GADFLY_ENDPOINT_M1 }}
GADFLY_ENDPOINT_M5: ${{ secrets.GADFLY_ENDPOINT_M5 }}
# ragnaros = the 4090 Ti, reached over the LAN through its llama-swap
# proxy (lazy-loads models on demand). Plain https URL, no credential —
# set here so the default `ragnaros/qwen3.6-27b` model resolves for all
# consumers. Registers provider "ragnaros". NB: use the un-hyphenated
# `llamaswap` provider spelling — the pinned image accepts that form.
GADFLY_ENDPOINT_RAGNAROS: "llamaswap|https://llama-swap.ragnaros.dudenhoeffer.casa"
# --- findings telemetry (optional) --------------------------------
GADFLY_FINDINGS_URL: ${{ secrets.GADFLY_FINDINGS_URL }}
GADFLY_FINDINGS_TOKEN: ${{ secrets.GADFLY_FINDINGS_TOKEN }}
# --- config (from inputs; empty => image default) -----------------
GADFLY_MODELS: ${{ inputs.models }}
GADFLY_SPECIALISTS: ${{ inputs.specialists }}
GADFLY_PROVIDER: ${{ inputs.provider }}
GADFLY_BASE_URL: ${{ inputs.base_url }}
GADFLY_PROVIDER_CONCURRENCY: ${{ inputs.provider_concurrency }}
GADFLY_PROVIDER_LENS_CONCURRENCY: ${{ inputs.provider_lens_concurrency }}
GADFLY_TIMEOUT_SECS: ${{ inputs.timeout_secs }}
GADFLY_MAX_STEPS: ${{ inputs.max_steps }}
GADFLY_WORKER_MODEL: ${{ inputs.worker_model }}
GADFLY_ALLOWED_USERS: ${{ inputs.allowed_users }}
GADFLY_TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}