name: Gatekeep description: > Parallel multi-model AI PR review across providers. Agreed findings become inline comments; disagreements stay in the summary. Fail-closed risk tier. Self-contained remote uses: git-url@ref. Provider secrets via free-form env names. author: Gatekeep inputs: github-token: description: Token with pull-requests + checks write required: true config-dir: description: > Optional consumer config directory (e.g. .github/gatekeep with providers.yaml). Empty = Action defaults/providers.yaml. required: false default: "" models: description: > Optional override of 2–3 models as comma-separated provider:model (or bare names unique in config). Empty = providers.yaml lists. required: false default: "" skip-draft: description: Skip draft PRs required: false default: "true" run-verify: description: Discover + run verify/benchmarks layer required: false default: "true" retain-artifacts: description: Upload last-good style artifacts from verify required: false default: "true" outputs: risk_tier: description: Final published risk tier value: ${{ steps.review.outputs.risk_tier }} security_blocking: description: Whether security check should block value: ${{ steps.review.outputs.security_blocking }} agreed_count: description: Count of multi-model agreed findings value: ${{ steps.review.outputs.agreed_count }} runs: using: composite steps: # Gitea forges often lack an actions/setup-node mirror (clone → 404). # Prefer runner-provided Node; only call setup-node when the mirror exists. - name: Setup Node (optional mirror) id: setup_node continue-on-error: true uses: actions/setup-node@v4 with: node-version: "20" - name: Require Node.js shell: bash run: | if ! command -v node >/dev/null 2>&1; then echo "::error::Node.js >= 18 required for Gatekeep (merge-findings.js / publish.js)." echo "Mirror actions/setup-node on this forge, preinstall Node on the runner, or add setup-node before uses: ./." exit 1 fi node --version # PEP 668 blocks pip --user on many runners; use a workspace venv instead. - name: Install PyYAML (providers.yaml) shell: bash run: | if python3 -c "import yaml" 2>/dev/null; then python3 -c "import yaml; print('pyyaml ok', yaml.__version__)" exit 0 fi VENV="${{ github.workspace }}/.gatekeep/.venv" mkdir -p "$(dirname "$VENV")" python3 -m venv "$VENV" "$VENV/bin/pip" install -q pyyaml echo "$VENV/bin" >> "$GITHUB_PATH" echo "VIRTUAL_ENV=$VENV" >> "$GITHUB_ENV" "$VENV/bin/python" -c "import yaml; print('pyyaml ok', yaml.__version__)" - name: Discover entry points id: discover shell: bash env: GATEKEEP_ROOT: ${{ github.workspace }} GATEKEEP_ACTION: ${{ github.action_path }} CONFIG_DIR: ${{ inputs.config-dir }} run: bash "${{ github.action_path }}/scripts/discover.sh" - name: Policy floor + security scan id: floor shell: bash env: GITHUB_TOKEN: ${{ inputs.github-token }} CONFIG_DIR: ${{ inputs.config-dir }} GATEKEEP_ROOT: ${{ github.workspace }} GATEKEEP_ACTION: ${{ github.action_path }} run: bash "${{ github.action_path }}/scripts/policy-floor.sh" - name: Parallel multi-model review id: review shell: bash env: GITHUB_TOKEN: ${{ inputs.github-token }} CONFIG_DIR: ${{ inputs.config-dir }} GATEKEEP_MODELS: ${{ inputs.models }} SKIP_DRAFT: ${{ inputs.skip-draft }} GATEKEEP_ROOT: ${{ github.workspace }} GATEKEEP_ACTION: ${{ github.action_path }} POLICY_FLOOR: ${{ steps.floor.outputs.floor }} POLICY_REASONS: ${{ steps.floor.outputs.reasons }} SCAN_HARD_FAIL: ${{ steps.floor.outputs.scan_hard_fail }} DISCOVER_JSON: ${{ steps.discover.outputs.discover_json }} # Provider API keys: set free-form names from providers.yaml `key` # on this step's env (inherited from the workflow step). Example: # env: # OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} # MY_CUSTOM_KEY: ${{ secrets.MY_CUSTOM_KEY }} run: | export PATH="$HOME/.local/bin:$PATH" # Ensure user-site packages (PyYAML) are importable export PYTHONPATH="$(python3 -c 'import site; print(site.getusersitepackages())'):${PYTHONPATH:-}" bash "${{ github.action_path }}/scripts/review.sh" - name: Verify layer (benchmarks) if: inputs.run-verify == 'true' id: verify shell: bash env: GATEKEEP_ROOT: ${{ github.workspace }} CONFIG_DIR: ${{ inputs.config-dir }} GATEKEEP_ACTION: ${{ github.action_path }} DISCOVER_JSON: ${{ steps.discover.outputs.discover_json }} run: bash "${{ github.action_path }}/scripts/verify-benchmarks.sh" - name: Retain artifacts if: inputs.retain-artifacts == 'true' && always() uses: actions/upload-artifact@v4 with: name: gatekeep-last-good-${{ github.run_id }} path: | ${{ github.workspace }}/.gatekeep/out/ ${{ github.workspace }}/.gatekeep/verify/ if-no-files-found: ignore retention-days: 14