feat(site): interactive 3D blueprint viewer for hardware diagrams #398
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Pull Request Validation | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: pr-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: true | |
| env: | |
| DOCKER_BUILDKIT: 1 | |
| COMPOSE_DOCKER_CLI_BUILD: 1 | |
| jobs: | |
| # Fork guard: block fork PRs from running on self-hosted runners with write perms | |
| fork-guard: | |
| name: Fork PR Guard | |
| runs-on: ubuntu-latest | |
| if: >- | |
| github.event_name != 'pull_request' || | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| steps: | |
| - run: echo "Not a fork PR - proceeding" | |
| # -- CI Stages (containerized) ------------------------------------------- | |
| ci: | |
| name: OASIS_OS CI | |
| needs: fork-guard | |
| runs-on: self-hosted | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Pre-checkout cleanup | |
| run: | | |
| for item in outputs target psp_output_file.log .git/index.lock; do | |
| if [ -d "$item" ] || [ -f "$item" ]; then | |
| docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \ | |
| "rm -rf /workspace/$item" 2>/dev/null || \ | |
| sudo rm -rf "$item" 2>/dev/null || true | |
| fi | |
| done | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| clean: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: ${{ github.head_ref }} | |
| - name: Set UID/GID | |
| run: | | |
| echo "USER_ID=$(id -u)" >> $GITHUB_ENV | |
| echo "GROUP_ID=$(id -g)" >> $GITHUB_ENV | |
| - name: Build CI Docker image | |
| run: docker compose --profile ci build rust-ci | |
| # -- Formatting ------------------------------------------------------- | |
| - name: Format check | |
| timeout-minutes: 5 | |
| run: docker compose --profile ci run --rm rust-ci cargo fmt --all -- --check | |
| # -- Linting ----------------------------------------------------------- | |
| - name: Clippy | |
| timeout-minutes: 10 | |
| run: docker compose --profile ci run --rm rust-ci cargo clippy --workspace -- -D warnings | |
| # -- Nightly Warnings Check (advisory) ----------------------------------- | |
| - name: Nightly warnings check | |
| timeout-minutes: 10 | |
| continue-on-error: true | |
| run: > | |
| docker compose --profile ci run --rm rust-ci | |
| cargo +nightly clippy --workspace -- -D warnings | |
| -W unused-comparisons | |
| # -- Documentation ------------------------------------------------------ | |
| - name: Doc build | |
| timeout-minutes: 10 | |
| run: > | |
| docker compose --profile ci run --rm | |
| -e RUSTDOCFLAGS="-D warnings" | |
| rust-ci cargo doc --workspace --no-deps | |
| # -- Markdown Link Check ------------------------------------------------ | |
| - name: Markdown link check | |
| timeout-minutes: 5 | |
| run: | | |
| set -o pipefail | |
| FAIL=0 | |
| # Check top-level markdown and docs/ (skip vendored target/node_modules) | |
| for target in *.md docs/ scripts/psp-scenarios.md; do | |
| [ -e "$target" ] || continue | |
| md-link-checker "$target" --internal-only 2>&1 \ | |
| | tee -a link_check_output.txt || FAIL=1 | |
| done | |
| if [ $FAIL -ne 0 ]; then | |
| echo "### Markdown Link Check" >> $GITHUB_STEP_SUMMARY | |
| grep -E '(broken link|->.*not found)' link_check_output.txt \ | |
| >> $GITHUB_STEP_SUMMARY 2>/dev/null || true | |
| fi | |
| exit $FAIL | |
| # -- Tests ------------------------------------------------------------- | |
| - name: Test | |
| timeout-minutes: 15 | |
| run: | | |
| set -o pipefail | |
| docker compose --profile ci run --rm rust-ci \ | |
| cargo test --workspace 2>&1 | tee test_output.txt | |
| # -- Build ------------------------------------------------------------- | |
| - name: Build (release) | |
| timeout-minutes: 15 | |
| run: docker compose --profile ci run --rm rust-ci cargo build --workspace --release | |
| # -- Screenshot Regression Tests ---------------------------------------- | |
| - name: Screenshot tests (generate) | |
| timeout-minutes: 5 | |
| run: | | |
| docker compose --profile ci run --rm \ | |
| -e SDL_VIDEODRIVER=dummy -e SDL_RENDER_DRIVER=software \ | |
| rust-ci cargo run -p oasis-app --bin screenshot-tests --release | |
| - name: Upload screenshot report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: screenshot-report | |
| path: screenshots/tests/ | |
| retention-days: 14 | |
| # -- License / Advisory ------------------------------------------------ | |
| - name: cargo-deny | |
| run: docker compose --profile ci run --rm rust-ci cargo deny check | |
| # -- Benchmarks (manual only via workflow_dispatch) ------------------------- | |
| - name: Download main branch baseline | |
| if: github.event_name == 'workflow_dispatch' | |
| continue-on-error: true | |
| uses: dawidd6/action-download-artifact@v6 | |
| with: | |
| workflow: main-ci.yml | |
| branch: main | |
| name: criterion-baseline | |
| path: baseline-criterion/ | |
| if_no_artifact_found: warn | |
| - name: Benchmarks | |
| if: github.event_name == 'workflow_dispatch' | |
| run: | | |
| set -o pipefail | |
| docker compose --profile ci run --rm rust-ci \ | |
| cargo bench --workspace 2>&1 | tee bench_results.txt | |
| echo "::group::Benchmark Results" | |
| grep -E '(time:|bench)' bench_results.txt || true | |
| echo "::endgroup::" | |
| - name: Benchmark regression check | |
| if: github.event_name == 'workflow_dispatch' | |
| run: | | |
| bash scripts/bench-compare.sh \ | |
| baseline-criterion/ \ | |
| target/criterion/ \ | |
| 10 \ | |
| >> $GITHUB_STEP_SUMMARY | |
| - name: Upload benchmark results | |
| if: github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: benchmark-results | |
| path: bench_results.txt | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| # -- Test Metrics ------------------------------------------------------- | |
| - name: Test metrics summary | |
| if: always() | |
| run: | | |
| if [ ! -f test_output.txt ]; then | |
| echo "No test output captured (test step may have been skipped)." | |
| echo "### Test Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "No test output available." >> $GITHUB_STEP_SUMMARY | |
| exit 0 | |
| fi | |
| PASS=$(grep -c '\.\.\. *ok$' test_output.txt 2>/dev/null) || PASS=0 | |
| FAIL=$(grep -c 'FAILED$' test_output.txt 2>/dev/null) || FAIL=0 | |
| TOTAL=$((PASS + FAIL)) | |
| echo "### Test Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Count |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Passed | $PASS |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Failed | $FAIL |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Total | $TOTAL |" >> $GITHUB_STEP_SUMMARY | |
| # -- PSP Backend Build ------------------------------------------------- | |
| - name: Setup PSP SDK | |
| uses: ./.github/actions/setup-psp | |
| - name: Build PSP EBOOT | |
| run: | | |
| cd crates/oasis-backend-psp | |
| RUST_PSP_BUILD_STD=1 cargo +nightly psp --release | |
| # -- PSP Emulator Test ------------------------------------------------- | |
| - name: Run OASIS_OS EBOOT in PPSSPPHeadless | |
| run: | | |
| docker compose --profile psp run --rm -e PPSSPP_HEADLESS=1 ppsspp \ | |
| /roms/release/EBOOT.PBP --timeout=30 2>/dev/null || true | |
| if [ -f psp_output_file.log ]; then | |
| cat psp_output_file.log | |
| else | |
| echo "No output log -- headless exited without crash (TIMEOUT ok)" | |
| fi | |
| - name: Fix Docker file ownership | |
| if: always() | |
| run: | | |
| for dir in target outputs; do | |
| if [ -d "$dir" ]; then | |
| docker run --rm -v "$(pwd)/$dir:/workspace" busybox:1.36.1 \ | |
| chown -Rh "$(id -u):$(id -g)" /workspace 2>/dev/null || true | |
| fi | |
| done | |
| # -- Gemini AI Code Review ----------------------------------------------- | |
| gemini-review: | |
| name: Gemini AI Code Review | |
| needs: fork-guard | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| !github.event.pull_request.draft | |
| runs-on: self-hosted | |
| timeout-minutes: 30 | |
| outputs: | |
| status: ${{ steps.review.outputs.status }} | |
| steps: | |
| - name: Pre-checkout cleanup | |
| run: | | |
| for item in outputs target psp_output_file.log .git/index.lock; do | |
| if [ -d "$item" ] || [ -f "$item" ]; then | |
| docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \ | |
| "rm -rf /workspace/$item" 2>/dev/null || \ | |
| sudo rm -rf "$item" 2>/dev/null || true | |
| fi | |
| done | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| clean: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: ${{ github.head_ref }} | |
| - name: Log commit info | |
| id: check-agent | |
| run: | | |
| LAST_AUTHOR=$(git log -1 --format='%an') | |
| echo "Last commit author: $LAST_AUTHOR" | |
| IS_AGENT="false" | |
| if [[ "$LAST_AUTHOR" == "AI Review Agent" ]] || \ | |
| [[ "$LAST_AUTHOR" == "AI Pipeline Agent" ]] || \ | |
| [[ "$LAST_AUTHOR" == "AI Agent Bot" ]]; then | |
| IS_AGENT="true" | |
| fi | |
| echo "is_agent_commit=$IS_AGENT" >> $GITHUB_OUTPUT | |
| - name: Run Gemini review | |
| id: review | |
| env: | |
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | |
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if ! command -v github-agents &>/dev/null; then | |
| echo "::warning::github-agents not found on PATH - skipping Gemini review" | |
| echo "status=skipped" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| set +e | |
| OUTPUT=$(github-agents pr-review "$PR_NUMBER" --editor 2>&1) | |
| EXIT_CODE=$? | |
| set -e | |
| echo "$OUTPUT" | |
| echo "$OUTPUT" > gemini-review.md | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| if echo "$OUTPUT" | grep -qiE '429|rate.?limit|quota|resource.?exhausted'; then | |
| echo "::warning::Gemini API rate limit hit - skipping review" | |
| echo "status=rate_limited" >> $GITHUB_OUTPUT | |
| exit 0 | |
| elif echo "$OUTPUT" | grep -qiE '503|502|service.?unavailable|ECONNREFUSED|ETIMEDOUT'; then | |
| echo "::warning::Gemini API unavailable - skipping review" | |
| echo "status=unavailable" >> $GITHUB_OUTPUT | |
| exit 0 | |
| elif echo "$OUTPUT" | grep -qiE 'panicked at|thread.*panic'; then | |
| echo "::warning::Gemini review tool crashed - skipping review" | |
| echo "status=tool_crash" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "status=failure" >> $GITHUB_OUTPUT | |
| exit $EXIT_CODE | |
| fi | |
| fi | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| - name: Upload review artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: gemini-review-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: gemini-review.md | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # -- Codex AI Code Review (DISABLED) -------------------------------------- | |
| # DISABLED: OpenAI Codex is being phased out due to security concerns. | |
| # OpenAI has entered partnerships with governments that enable mass surveillance | |
| # and autonomous weapons decision-making. This poses unacceptable risk. | |
| # We strongly recommend Anthropic (Claude) models instead. | |
| codex-review: | |
| name: Codex AI Code Review (DISABLED) | |
| needs: [fork-guard, gemini-review] | |
| if: contains(github.event.pull_request.labels.*.name, 'enable-codex') # Permanently disabled -- see security notice above | |
| runs-on: self-hosted | |
| timeout-minutes: 15 | |
| continue-on-error: true | |
| outputs: | |
| status: ${{ steps.review.outputs.status }} | |
| steps: | |
| - name: Pre-checkout cleanup | |
| run: | | |
| for item in outputs target psp_output_file.log .git/index.lock; do | |
| if [ -d "$item" ] || [ -f "$item" ]; then | |
| docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \ | |
| "rm -rf /workspace/$item" 2>/dev/null || \ | |
| sudo rm -rf "$item" 2>/dev/null || true | |
| fi | |
| done | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| clean: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: ${{ github.head_ref }} | |
| - name: Run Codex review | |
| id: review | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if ! command -v github-agents &>/dev/null; then | |
| echo "::warning::github-agents not found on PATH - skipping Codex review" | |
| echo "status=skipped" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| set +e | |
| OUTPUT=$(github-agents pr-review "$PR_NUMBER" --agent codex 2>&1) | |
| EXIT_CODE=$? | |
| set -e | |
| echo "$OUTPUT" | |
| echo "$OUTPUT" > codex-review.md | |
| if [ $EXIT_CODE -ne 0 ]; then | |
| if echo "$OUTPUT" | grep -qiE '429|rate.?limit|quota|resource.?exhausted'; then | |
| echo "::warning::Codex API rate limit hit" | |
| echo "status=rate_limited" >> $GITHUB_OUTPUT | |
| exit 0 | |
| elif echo "$OUTPUT" | grep -qiE '503|502|service.?unavailable|ECONNREFUSED|ETIMEDOUT'; then | |
| echo "::warning::Codex API unavailable" | |
| echo "status=unavailable" >> $GITHUB_OUTPUT | |
| exit 0 | |
| elif echo "$OUTPUT" | grep -qiE 'panicked at|thread.*panic'; then | |
| echo "::warning::Codex review tool crashed - skipping review" | |
| echo "status=tool_crash" >> $GITHUB_OUTPUT | |
| exit 0 | |
| else | |
| echo "status=failure" >> $GITHUB_OUTPUT | |
| exit $EXIT_CODE | |
| fi | |
| fi | |
| echo "status=success" >> $GITHUB_OUTPUT | |
| - name: Upload review artifact | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: codex-review-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: codex-review.md | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # -- Agent Review Response (responds to Gemini/Codex feedback) ----------- | |
| agent-review-response: | |
| name: Agent Review Response | |
| needs: [ci, gemini-review, codex-review] | |
| if: | | |
| always() && | |
| !cancelled() && | |
| github.event_name == 'pull_request' && | |
| !github.event.pull_request.draft && | |
| !contains(github.event.pull_request.labels.*.name, 'no-auto-fix') | |
| runs-on: self-hosted | |
| timeout-minutes: 30 | |
| outputs: | |
| made_changes: ${{ steps.agent-fix.outputs.made_changes }} | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| GITHUB_TOKEN: ${{ secrets.AGENT_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| steps: | |
| - name: Pre-checkout cleanup | |
| run: | | |
| for item in outputs target psp_output_file.log .git/index.lock; do | |
| if [ -d "$item" ] || [ -f "$item" ]; then | |
| docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \ | |
| "rm -rf /workspace/$item" 2>/dev/null || \ | |
| sudo rm -rf "$item" 2>/dev/null || true | |
| fi | |
| done | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| clean: false | |
| token: ${{ secrets.AGENT_TOKEN }} | |
| ref: ${{ github.head_ref }} | |
| - name: Ensure clean working directory | |
| run: | | |
| git checkout -- . 2>/dev/null || true | |
| git clean -fd 2>/dev/null || true | |
| - name: Check iteration count | |
| id: iteration | |
| uses: ./.github/actions/agent-iteration-check | |
| with: | |
| pr_number: ${{ github.event.pull_request.number }} | |
| max_iterations: '5' | |
| agent_type: 'review-fix' | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Skip if max iterations reached | |
| if: steps.iteration.outputs.exceeded_max == 'true' | |
| run: | | |
| echo "Maximum iterations (5) reached for review-fix agent. Manual intervention required." | |
| echo "made_changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| - name: Download review artifacts | |
| if: steps.iteration.outputs.should_skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| pattern: '*-review-${{ github.run_id }}-${{ github.run_attempt }}' | |
| merge-multiple: true | |
| path: . | |
| - name: Run agent review response | |
| id: agent-fix | |
| if: steps.iteration.outputs.should_skip != 'true' | |
| env: | |
| GEMINI_REVIEW_PATH: gemini-review.md | |
| CODEX_REVIEW_PATH: "" # Codex disabled -- see security notice | |
| BRANCH_NAME: ${{ github.head_ref }} | |
| ITERATION_COUNT: ${{ steps.iteration.outputs.iteration_count }} | |
| run: | | |
| if ! command -v automation-cli &>/dev/null; then | |
| echo "::warning::automation-cli not found on PATH - skipping review response" | |
| echo "made_changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Running agent review response..." | |
| automation-cli review respond \ | |
| "$PR_NUMBER" \ | |
| "$BRANCH_NAME" \ | |
| "$ITERATION_COUNT" \ | |
| "5" | |
| # -- Agent Failure Handler ----------------------------------------------- | |
| agent-failure-handler: | |
| name: Agent Failure Handler | |
| needs: [ci, gemini-review, codex-review, agent-review-response] | |
| if: | | |
| failure() && | |
| github.event_name == 'pull_request' && | |
| !github.event.pull_request.draft && | |
| !contains(github.event.pull_request.labels.*.name, 'no-auto-fix') && | |
| needs.ci.result == 'failure' | |
| runs-on: self-hosted | |
| timeout-minutes: 30 | |
| outputs: | |
| made_changes: ${{ steps.agent-fix.outputs.made_changes }} | |
| env: | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| GITHUB_TOKEN: ${{ secrets.AGENT_TOKEN }} | |
| GITHUB_REPOSITORY: ${{ github.repository }} | |
| steps: | |
| - name: Pre-checkout cleanup | |
| run: | | |
| for item in outputs target psp_output_file.log .git/index.lock; do | |
| if [ -d "$item" ] || [ -f "$item" ]; then | |
| docker run --rm -v "$(pwd):/workspace" busybox:1.36.1 sh -c \ | |
| "rm -rf /workspace/$item" 2>/dev/null || \ | |
| sudo rm -rf "$item" 2>/dev/null || true | |
| fi | |
| done | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| clean: false | |
| token: ${{ secrets.AGENT_TOKEN }} | |
| ref: ${{ github.head_ref }} | |
| - name: Ensure clean working directory | |
| run: | | |
| git checkout -- . 2>/dev/null || true | |
| git clean -fd 2>/dev/null || true | |
| - name: Check iteration count | |
| id: iteration | |
| uses: ./.github/actions/agent-iteration-check | |
| with: | |
| pr_number: ${{ github.event.pull_request.number }} | |
| max_iterations: '5' | |
| agent_type: 'failure-fix' | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Skip if max iterations reached | |
| if: steps.iteration.outputs.exceeded_max == 'true' | |
| run: | | |
| echo "Maximum iterations (5) reached for failure-fix agent. Manual intervention required." | |
| echo "made_changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| - name: Run agent failure handler | |
| id: agent-fix | |
| if: steps.iteration.outputs.exceeded_max != 'true' | |
| env: | |
| BRANCH_NAME: ${{ github.head_ref }} | |
| ITERATION_COUNT: ${{ steps.iteration.outputs.iteration_count }} | |
| run: | | |
| if ! command -v automation-cli &>/dev/null; then | |
| echo "::warning::automation-cli not found on PATH - skipping failure handler" | |
| echo "made_changes=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "Running agent failure handler..." | |
| automation-cli review failure \ | |
| "$PR_NUMBER" \ | |
| "$BRANCH_NAME" \ | |
| "$ITERATION_COUNT" \ | |
| "5" \ | |
| "format,lint,test" | |
| # -- PR Status Summary --------------------------------------------------- | |
| pr-status: | |
| name: PR Status Summary | |
| needs: [ci, gemini-review, codex-review, agent-review-response, agent-failure-handler] | |
| if: always() | |
| runs-on: self-hosted | |
| steps: | |
| - name: Generate status summary | |
| run: | | |
| echo "## PR Validation Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| CI | ${{ needs.ci.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Gemini Review | ${{ needs.gemini-review.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Codex Review | disabled (security) |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Review Response | ${{ needs.agent-review-response.result }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Failure Handler | ${{ needs.agent-failure-handler.result }} |" >> $GITHUB_STEP_SUMMARY | |
| # CI failure is blocking; reviews are advisory | |
| if [[ "${{ needs.ci.result }}" == "failure" ]]; then | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "CI failed - please review the logs" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "PR validation completed successfully" >> $GITHUB_STEP_SUMMARY |