From 84adf67ad75e84d351a38d4b953a8fbf5bdf8563 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 30 Apr 2026 09:45:06 +0800 Subject: [PATCH 01/11] feat: add regression-test-missing and pr-checks composite actions Add two new composite actions: - regression-test-missing: detects PRs that fix bugs or modify behavior but lack corresponding regression tests - pr-checks: combines review, feature-missing, and regression-test-missing checks into a single opencode invocation Update ci.yml smoke test to cover both new actions. Closes #45 --- .github/workflows/ci.yml | 20 +++ pr-checks/action.yml | 230 +++++++++++++++++++++++++++++ regression-test-missing/action.yml | 227 ++++++++++++++++++++++++++++ 3 files changed, 477 insertions(+) create mode 100644 pr-checks/action.yml create mode 100644 regression-test-missing/action.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71017c1..d78cf88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,6 +61,26 @@ jobs: github-token: smoke-gh-token zhipu-api-key: smoke-zhipu-token + - name: Run regression-test-missing action + uses: ./regression-test-missing + with: + install-url: http://127.0.0.1:8765/fake-installer.sh + cache: false + install-attempts: 1 + attempts: 1 + github-token: smoke-gh-token + zhipu-api-key: smoke-zhipu-token + + - name: Run pr-checks action + uses: ./pr-checks + with: + install-url: http://127.0.0.1:8765/fake-installer.sh + cache: false + install-attempts: 1 + attempts: 1 + github-token: smoke-gh-token + zhipu-api-key: smoke-zhipu-token + - name: Stop fake installer server if: always() run: | diff --git a/pr-checks/action.yml b/pr-checks/action.yml new file mode 100644 index 0000000..a7897cc --- /dev/null +++ b/pr-checks/action.yml @@ -0,0 +1,230 @@ +name: OpenCode PR Checks +description: Runs all three OpenCode PR checks (review, feature-missing, regression-test-missing) in a single opencode invocation. + +inputs: + model: + description: Value exported as MODEL before running `opencode github run`. + required: false + default: "" + install-url: + description: Installer URL used to bootstrap OpenCode. + required: false + default: https://opencode.ai/install + install-dir: + description: Directory where the opencode binary should be installed. + required: false + default: "" + xdg-cache-home: + description: Dedicated XDG cache directory for OpenCode. + required: false + default: "" + cache: + description: Cache the install and XDG cache directories with actions/cache. + required: false + default: "true" + cache-key: + description: Cache key suffix used to invalidate installer-based caches. + required: false + default: v1 + install-attempts: + description: Number of installer retry attempts. + required: false + default: "3" + allow-preinstalled: + description: Reuse an existing opencode found on PATH instead of forcing installer bootstrap. + required: false + default: "false" + version: + description: Minimum required OpenCode version (semver). Empty string disables version checking. + required: false + default: "1.4.5" + working-directory: + description: Optional working directory before running opencode. + required: false + default: "" + attempts: + description: Total number of attempts before failing. + required: false + default: "3" + retry-profile: + description: Built-in retry profile, defaults to github-network for common GitHub usage. + required: false + default: github-network + retry-on-regex: + description: Retry only when stderr or stdout matches this regex. + required: false + default: "" + retry-delay-seconds: + description: Base delay between retries in seconds. + required: false + default: "15" + timeout-seconds: + description: Maximum execution time in seconds for `opencode github run`. Set 0 to disable. + required: false + default: "600" + prompt: + description: Value exported as PROMPT before running `opencode github run`. Defaults to a combined prompt that runs all three checks. + required: false + default: | + Run all three PR checks on this pull request (read-only mode, DO NOT modify any code). + + Execute these checks in order and combine the results: + + ## Check 1: Code Review + Review the code for: + - Code quality issues + - Potential bugs or logic errors + - Code style consistency + - Security concerns + - Performance issues + + ## Check 2: Feature Missing + 1. Find linked issues via `gh pr view --json closingIssuesReferences,title,body`. + 2. If linked issues exist, read each issue body as the feature spec. + 3. If no linked issues, extract requirements from the PR title and body. + 4. Compare the spec/requirements against the implementation. + Focus on: missing features, partial implementations, integration gaps, missing edge case handling. + Do NOT report: code style issues, bugs in existing code, suggestions beyond the spec. + + ## Check 3: Regression Test Missing + 1. Determine PR type: BUGFIX / BEHAVIOR_CHANGE / NEW_FEATURE / CHORE. + 2. For BUGFIX and BEHAVIOR_CHANGE PRs, check if regression tests exist. + 3. For NEW_FEATURE and CHORE PRs, no regression tests needed. + Focus on: bug fixes without reproducers, behavior changes without coverage, missing edge case tests. + Do NOT report: code style, missing features from spec, test suggestions for trivial changes. + + Please respond in Chinese. DO NOT modify any code, only provide analysis. + + Output format - three sections: + + ### 代码审查 + - Decision: 可合并 / 有条件合并 / 不可合并 + - Summary + - 阻塞项 (blocking issues, or "阻塞项:无") + - 建议项 (suggestions, or "建议项:无") + + ### 功能遗漏 + - Decision: 无遗漏 / 发现遗漏 + - PR type detected + - If gaps found, list each by severity (CRITICAL / MEDIUM / LOW) + + ### 回归测试 + - Decision: 无需回归测试 / 缺少回归测试 + - PR type detected + - If tests missing, list each gap with suggested test case description and severity (CRITICAL / MEDIUM / LOW) + use-github-token: + description: Value exported as USE_GITHUB_TOKEN before running `opencode github run`. + required: false + default: "true" + github-token: + description: Value exported as GITHUB_TOKEN before running `opencode github run`. + required: false + default: "" + zhipu-api-key: + description: Value exported as ZHIPU_API_KEY before running `opencode github run`. + required: false + default: "" + opencode-go-api-key: + description: Value exported as OPENCODE_API_KEY before running `opencode github run`. The opencode-go provider shares this env var with OpenCode Zen. + required: false + default: "" + deepseek-api-key: + description: Value exported as DEEPSEEK_API_KEY before running `opencode github run`. + required: false + default: "" + fallback-models: + description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. + required: false + default: "" + model-timeout-seconds: + description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. + required: false + default: "300" + fallback-on-regex: + description: Rotate to the next fallback model when output matches this regex. + required: false + default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out + +runs: + using: composite + steps: + - if: ${{ runner.os != 'Linux' }} + shell: bash + run: | + set -euo pipefail + printf 'pr-checks currently supports Linux runners only\n' >&2 + exit 1 + + - id: paths + shell: bash + env: + INPUT_INSTALL_DIR: ${{ inputs.install-dir }} + INPUT_XDG_CACHE_HOME: ${{ inputs.xdg-cache-home }} + run: | + set -euo pipefail + install_dir="$INPUT_INSTALL_DIR" + xdg_cache_home="$INPUT_XDG_CACHE_HOME" + + if [[ -z "$install_dir" ]]; then + install_dir="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/bin" + fi + + if [[ -z "$xdg_cache_home" ]]; then + xdg_cache_home="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/cache" + fi + + printf 'install_dir=%s\n' "$install_dir" >>"$GITHUB_OUTPUT" + printf 'xdg_cache_home=%s\n' "$xdg_cache_home" >>"$GITHUB_OUTPUT" + + - id: key + shell: bash + env: + INPUT_INSTALL_URL: ${{ inputs.install-url }} + run: | + set -euo pipefail + install_url_hash="$(printf '%s' "$INPUT_INSTALL_URL" | sha256sum | cut -d ' ' -f1)" + printf 'install_url_hash=%s\n' "$install_url_hash" >>"$GITHUB_OUTPUT" + + - id: cache + if: ${{ inputs.cache == 'true' }} + uses: actions/cache@v5 + with: + path: | + ${{ steps.paths.outputs.install_dir }} + ${{ steps.paths.outputs.xdg_cache_home }} + key: pr-checks-opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ inputs.version }}-${{ inputs.cache-key }} + + - shell: bash + env: + OPENCODE_INSTALL_DIR: ${{ steps.paths.outputs.install_dir }} + XDG_CACHE_HOME: ${{ steps.paths.outputs.xdg_cache_home }} + OPENCODE_INSTALL_URL: ${{ inputs.install-url }} + OPENCODE_INSTALL_ATTEMPTS: ${{ inputs.install-attempts }} + OPENCODE_ALLOW_PREINSTALLED: ${{ inputs.allow-preinstalled }} + OPENCODE_MIN_VERSION: ${{ inputs.version }} + run: ${{ github.action_path }}/../setup-opencode/install-opencode.sh + + - shell: bash + env: + GITHUB_RUN_OPENCODE_WORKING_DIRECTORY: ${{ inputs.working-directory }} + GITHUB_RUN_OPENCODE_ATTEMPTS: ${{ inputs.attempts }} + GITHUB_RUN_OPENCODE_RETRY_PROFILE: ${{ inputs.retry-profile }} + GITHUB_RUN_OPENCODE_RETRY_ON_REGEX: ${{ inputs.retry-on-regex }} + GITHUB_RUN_OPENCODE_RETRY_DELAY_SECONDS: ${{ inputs.retry-delay-seconds }} + GITHUB_RUN_OPENCODE_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }} + GITHUB_RUN_OPENCODE_MODEL: ${{ inputs.model }} + GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} + GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} + GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} + GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} + GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} + GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} + GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} + GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} + GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} + run: | + if ! command -v python3 >/dev/null 2>&1; then + printf 'python3 is required but not installed on this runner\n' >&2 + exit 1 + fi + python3 ${{ github.action_path }}/../github-run-opencode/run-github-opencode.py diff --git a/regression-test-missing/action.yml b/regression-test-missing/action.yml new file mode 100644 index 0000000..f359d8a --- /dev/null +++ b/regression-test-missing/action.yml @@ -0,0 +1,227 @@ +name: OpenCode Regression Test Missing +description: Detects when a PR fixes bugs or modifies behavior but lacks corresponding regression tests. + +inputs: + install-url: + description: Installer URL used to bootstrap OpenCode. + required: false + default: https://opencode.ai/install + install-dir: + description: Directory where the opencode binary should be installed. + required: false + default: "" + xdg-cache-home: + description: Dedicated XDG cache directory for OpenCode. + required: false + default: "" + cache: + description: Cache the install and XDG cache directories with actions/cache. + required: false + default: "true" + cache-key: + description: Cache key suffix used to invalidate installer-based caches. + required: false + default: v1 + install-attempts: + description: Number of installer retry attempts. + required: false + default: "3" + allow-preinstalled: + description: Reuse an existing opencode found on PATH instead of forcing installer bootstrap. + required: false + default: "false" + version: + description: Minimum required OpenCode version (semver). If the cached binary is older, it will be reinstalled. Empty string disables version checking. + required: false + default: "1.4.5" + working-directory: + description: Optional working directory before running opencode. + required: false + default: "" + attempts: + description: Total number of attempts before failing. + required: false + default: "3" + retry-profile: + description: Built-in retry profile, defaults to github-network for common GitHub usage. + required: false + default: github-network + retry-on-regex: + description: Retry only when stderr or stdout matches this regex. + required: false + default: "" + retry-delay-seconds: + description: Base delay between retries in seconds. + required: false + default: "15" + timeout-seconds: + description: Maximum execution time in seconds for `opencode github run`. Set 0 to disable. + required: false + default: "600" + model: + description: Value exported as MODEL before running `opencode github run`. + required: false + default: "" + prompt: + description: Value exported as PROMPT before running `opencode github run`. + required: false + default: | + Audit this pull request for missing regression tests (read-only mode, DO NOT modify any code). + + Steps: + 1. Read the PR title, body, and labels to understand the intent (bug fix, refactor, behavior change, or pure new feature). + 2. If there are linked issues, read them via `gh issue view` to understand the original problem. + 3. Read the PR's changed files to understand what was modified. + 4. Check whether test files were included in the PR changes (look for files in test directories, files with test/spec in the name, or new test cases added to existing test files). + 5. For each bug fix or behavior change found, evaluate whether a regression test exists that would catch the same issue if it were reintroduced. + + Determine the PR type first: + - BUGFIX: The PR description or linked issues indicate a bug is being fixed (crashes, incorrect output, missing error handling, etc.) + - BEHAVIOR_CHANGE: The PR modifies existing behavior, changes APIs, or refactors core logic that could break existing functionality + - NEW_FEATURE: The PR only adds new functionality without modifying existing behavior + - CHORE: The PR only touches documentation, config files, or formatting + + Only flag missing tests for BUGFIX and BEHAVIOR_CHANGE PR types. + For NEW_FEATURE and CHORE PRs, respond with "无需回归测试" and explain why. + + Focus on finding: + - Bug fixes without a reproducer or regression test that verifies the fix + - Behavior changes without tests covering the old behavior being changed + - Error handling added without a test that triggers the error condition + - Edge case fixes without tests exercising those edge cases + - API changes without tests verifying backward compatibility (if applicable) + + Do NOT report: + - Code style, naming, or formatting issues (that's the review action's job) + - Missing features from the spec (that's the feature-missing action's job) + - Test coverage suggestions for brand new features with no existing behavior change + - Suggestions to add tests for trivially correct changes (e.g., typo fixes in comments) + + Please respond in Chinese. DO NOT modify any code, only provide analysis. + + Output format: + - First line: one of "无需回归测试" (no regression tests needed) or "缺少回归测试" (regression tests missing) + - Then state the detected PR type (BUGFIX / BEHAVIOR_CHANGE / NEW_FEATURE / CHORE) + - Then a short summary of the analysis + - If regression tests are missing, list each gap with: + - What bug/behavior change was made + - Why a regression test is needed + - Suggested test case description (what it should verify, not the actual code) + - Severity: CRITICAL (data loss, security, crash without test) / MEDIUM (incorrect behavior without test) / LOW (minor edge case without test) + - If no regression tests are needed, briefly explain why (e.g., "纯新增功能,未修改现有行为") + use-github-token: + description: Value exported as USE_GITHUB_TOKEN before running `opencode github run`. + required: false + default: "true" + github-token: + description: Value exported as GITHUB_TOKEN before running `opencode github run`. + required: false + default: "" + zhipu-api-key: + description: Value exported as ZHIPU_API_KEY before running `opencode github run`. + required: false + default: "" + opencode-go-api-key: + description: Value exported as OPENCODE_API_KEY before running `opencode github run`. The opencode-go provider shares this env var with OpenCode Zen. + required: false + default: "" + deepseek-api-key: + description: Value exported as DEEPSEEK_API_KEY before running `opencode github run`. + required: false + default: "" + fallback-models: + description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. + required: false + default: "" + model-timeout-seconds: + description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. + required: false + default: "300" + fallback-on-regex: + description: Rotate to the next fallback model when output matches this regex. + required: false + default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out + +runs: + using: composite + steps: + - if: ${{ runner.os != 'Linux' }} + shell: bash + run: | + set -euo pipefail + printf 'regression-test-missing currently supports Linux runners only\n' >&2 + exit 1 + + - id: paths + shell: bash + env: + INPUT_INSTALL_DIR: ${{ inputs.install-dir }} + INPUT_XDG_CACHE_HOME: ${{ inputs.xdg-cache-home }} + run: | + set -euo pipefail + install_dir="$INPUT_INSTALL_DIR" + xdg_cache_home="$INPUT_XDG_CACHE_HOME" + + if [[ -z "$install_dir" ]]; then + install_dir="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/bin" + fi + + if [[ -z "$xdg_cache_home" ]]; then + xdg_cache_home="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/cache" + fi + + printf 'install_dir=%s\n' "$install_dir" >>"$GITHUB_OUTPUT" + printf 'xdg_cache_home=%s\n' "$xdg_cache_home" >>"$GITHUB_OUTPUT" + + - id: key + shell: bash + env: + INPUT_INSTALL_URL: ${{ inputs.install-url }} + run: | + set -euo pipefail + install_url_hash="$(printf '%s' "$INPUT_INSTALL_URL" | sha256sum | cut -d ' ' -f1)" + printf 'install_url_hash=%s\n' "$install_url_hash" >>"$GITHUB_OUTPUT" + + - id: cache + if: ${{ inputs.cache == 'true' }} + uses: actions/cache@v5 + with: + path: | + ${{ steps.paths.outputs.install_dir }} + ${{ steps.paths.outputs.xdg_cache_home }} + key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ inputs.version }}-${{ inputs.cache-key }} + + - shell: bash + env: + OPENCODE_INSTALL_DIR: ${{ steps.paths.outputs.install_dir }} + XDG_CACHE_HOME: ${{ steps.paths.outputs.xdg_cache_home }} + OPENCODE_INSTALL_URL: ${{ inputs.install-url }} + OPENCODE_INSTALL_ATTEMPTS: ${{ inputs.install-attempts }} + OPENCODE_ALLOW_PREINSTALLED: ${{ inputs.allow-preinstalled }} + OPENCODE_MIN_VERSION: ${{ inputs.version }} + run: ${{ github.action_path }}/../setup-opencode/install-opencode.sh + + - shell: bash + env: + GITHUB_RUN_OPENCODE_WORKING_DIRECTORY: ${{ inputs.working-directory }} + GITHUB_RUN_OPENCODE_ATTEMPTS: ${{ inputs.attempts }} + GITHUB_RUN_OPENCODE_RETRY_PROFILE: ${{ inputs.retry-profile }} + GITHUB_RUN_OPENCODE_RETRY_ON_REGEX: ${{ inputs.retry-on-regex }} + GITHUB_RUN_OPENCODE_RETRY_DELAY_SECONDS: ${{ inputs.retry-delay-seconds }} + GITHUB_RUN_OPENCODE_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }} + GITHUB_RUN_OPENCODE_MODEL: ${{ inputs.model }} + GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} + GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} + GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} + GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} + GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} + GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} + GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} + GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} + GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} + run: | + if ! command -v python3 >/dev/null 2>&1; then + printf 'python3 is required but not installed on this runner\n' >&2 + exit 1 + fi + python3 ${{ github.action_path }}/../github-run-opencode/run-github-opencode.py From 3a3dff5b660fc34c71207cc4031112d60bf8d48f Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 30 Apr 2026 09:57:12 +0800 Subject: [PATCH 02/11] fix: address review feedback on regression-test-missing and pr-checks - Fix cache key prefix: use regression-test-missing-opencode- prefix - Fix version management: use setup-opencode/default-version instead of hardcoded value, matching review action pattern - Replace pr-checks composite action with proper workflow file containing 3 parallel jobs (review, feature-missing, regression-test-missing) - Add .github/workflows/regression-test-missing.yml standalone workflow - Remove pr-checks/ directory (now a workflow, not a composite action) - Update README with regression-test-missing docs and pr-checks workflow reference - Update ci.yml smoke test description --- .github/workflows/ci.yml | 10 - .github/workflows/pr-checks.yml | 84 +++++++ .github/workflows/regression-test-missing.yml | 33 +++ README.md | 23 +- pr-checks/action.yml | 230 ------------------ regression-test-missing/action.yml | 29 ++- 6 files changed, 162 insertions(+), 247 deletions(-) create mode 100644 .github/workflows/pr-checks.yml create mode 100644 .github/workflows/regression-test-missing.yml delete mode 100644 pr-checks/action.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d78cf88..51b1e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,16 +71,6 @@ jobs: github-token: smoke-gh-token zhipu-api-key: smoke-zhipu-token - - name: Run pr-checks action - uses: ./pr-checks - with: - install-url: http://127.0.0.1:8765/fake-installer.sh - cache: false - install-attempts: 1 - attempts: 1 - github-token: smoke-gh-token - zhipu-api-key: smoke-zhipu-token - - name: Stop fake installer server if: always() run: | diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..dd8b550 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,84 @@ +name: OpenCode PR Checks + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + review: + name: Code Review + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + steps: + - name: Checkout PR head + uses: actions/checkout@v6 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + + - name: Run OpenCode review + uses: Svtter/opencode-actions/review@v2 + with: + model: ${{ vars.MODEL_NAME }} + github-token: ${{ secrets.GITHUB_TOKEN }} + zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} + deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }} + opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }} + + feature-missing: + name: Feature Missing + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: write + issues: write + steps: + - uses: actions/checkout@v6 + with: + clean: true + persist-credentials: true + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + + - name: Run feature missing audit + uses: Svtter/opencode-actions/feature-missing@v2 + with: + model: ${{ vars.MODEL_NAME }} + github-token: ${{ secrets.GITHUB_TOKEN }} + zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} + deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }} + opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }} + + regression-test-missing: + name: Regression Test Missing + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: write + issues: write + steps: + - uses: actions/checkout@v6 + with: + clean: true + persist-credentials: true + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + + - name: Run regression test missing audit + uses: Svtter/opencode-actions/regression-test-missing@v2 + with: + model: ${{ vars.MODEL_NAME }} + github-token: ${{ secrets.GITHUB_TOKEN }} + zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} + deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }} + opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }} diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml new file mode 100644 index 0000000..8b474be --- /dev/null +++ b/.github/workflows/regression-test-missing.yml @@ -0,0 +1,33 @@ +name: OpenCode PR Regression Test Missing + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + regression-test-missing: + name: Regression Test Missing + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + pull-requests: write + issues: write + steps: + - uses: actions/checkout@v6 + with: + clean: true + persist-credentials: true + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + + - name: Run regression test missing audit + uses: Svtter/opencode-actions/regression-test-missing@v2 + with: + model: ${{ vars.MODEL_NAME }} + github-token: ${{ secrets.GITHUB_TOKEN }} + zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} + deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }} + opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }} diff --git a/README.md b/README.md index b848ca4..c9dd7fe 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ npx skills add sun-praise/opencode-actions - `review`: opinionated PR review wrapper with built-in prompt and model defaults - `feature-missing`: audits PR implementation against linked issue spec to find missing features - `spec-coverage`: cross-references project spec/task files against PR implementation to find planned but unimplemented features +- `regression-test-missing`: detects PRs that fix bugs or modify behavior but lack regression tests - `github-run-opencode`: one-step wrapper for the common `opencode github run` workflow - `setup-opencode`: installs OpenCode, restores a dedicated cache, and exports the binary path - `run-opencode`: runs `opencode` with optional retry logic for flaky GitHub network failures @@ -118,19 +119,34 @@ Unlike `feature-missing` (which checks PR self-described scope), `spec-coverage` ```yaml - name: Run spec coverage audit uses: Svtter/opencode-actions/spec-coverage@v2 +``` + +## regression-test-missing + +Use this alongside `review` and `feature-missing` to detect PRs that fix bugs or modify existing behavior but lack regression tests. + +- classifies the PR as BUGFIX, BEHAVIOR_CHANGE, NEW_FEATURE, or CHORE +- only flags missing tests for BUGFIX and BEHAVIOR_CHANGE PRs +- suggests specific test cases that would catch regressions +- shares the same inputs and cache as `review`/`feature-missing` + +```yaml +- name: Run regression test missing audit + uses: Svtter/opencode-actions/regression-test-missing@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }} ``` -### How the three review actions differ +### How the four review actions differ | Action | Scope source | What it catches | | --- | --- | --- | | `review` | PR diff | Code quality, security, bugs | | `feature-missing` | PR title/body + linked issues | PR self-described scope completeness | | `spec-coverage` | Project spec/task files | Full planned scope vs implementation | +| `regression-test-missing` | PR diff + classification | Missing regression tests for bug fixes and behavior changes | ## setup-opencode @@ -185,6 +201,7 @@ Public consumers should reference the subdirectory action path: uses: Svtter/opencode-actions/review@v2 uses: Svtter/opencode-actions/feature-missing@v2 uses: Svtter/opencode-actions/spec-coverage@v2 +uses: Svtter/opencode-actions/regression-test-missing@v2 uses: Svtter/opencode-actions/github-run-opencode@v2 uses: Svtter/opencode-actions/setup-opencode@v2 uses: Svtter/opencode-actions/run-opencode@v2 @@ -219,7 +236,7 @@ This repository includes a CI workflow that: - runs `shellcheck` on every bundled shell script - runs the local shell-based regression suite -- smoke-tests all actions through `uses: ./setup-opencode`, `uses: ./run-opencode`, `uses: ./github-run-opencode`, `uses: ./review`, `uses: ./feature-missing`, and `uses: ./spec-coverage` +- smoke-tests all actions through `uses: ./setup-opencode`, `uses: ./run-opencode`, `uses: ./github-run-opencode`, `uses: ./review`, `uses: ./feature-missing`, `uses: ./spec-coverage`, and `uses: ./regression-test-missing` ## Release Policy @@ -234,7 +251,7 @@ This repository includes a CI workflow that: 2. Verify `CI` passes on `main`. 3. Create a GitHub release with a semver tag such as `v1.0.0`. 4. Confirm the `Update Major Tag` workflow moved `v1` to that release. -5. Use `owner/repo/review@v2` for the simplest review setup, `owner/repo/feature-missing@v2` for PR scope audit, `owner/repo/spec-coverage@v2` for spec coverage audit, `owner/repo/github-run-opencode@v2` for generic `github run`, or `owner/repo/setup-opencode@v2` plus `owner/repo/run-opencode@v2` for more control. +5. Use `owner/repo/review@v2` for the simplest review setup, `owner/repo/feature-missing@v2` for spec coverage audit, `owner/repo/spec-coverage@v2` for spec coverage audit, `owner/repo/regression-test-missing@v2` for regression test audit, `owner/repo/github-run-opencode@v2` for generic `github run`, or `owner/repo/setup-opencode@v2` plus `owner/repo/run-opencode@v2` for more control. The initial release-notes template lives at `docs/releases/v1.0.0.md`. diff --git a/pr-checks/action.yml b/pr-checks/action.yml deleted file mode 100644 index a7897cc..0000000 --- a/pr-checks/action.yml +++ /dev/null @@ -1,230 +0,0 @@ -name: OpenCode PR Checks -description: Runs all three OpenCode PR checks (review, feature-missing, regression-test-missing) in a single opencode invocation. - -inputs: - model: - description: Value exported as MODEL before running `opencode github run`. - required: false - default: "" - install-url: - description: Installer URL used to bootstrap OpenCode. - required: false - default: https://opencode.ai/install - install-dir: - description: Directory where the opencode binary should be installed. - required: false - default: "" - xdg-cache-home: - description: Dedicated XDG cache directory for OpenCode. - required: false - default: "" - cache: - description: Cache the install and XDG cache directories with actions/cache. - required: false - default: "true" - cache-key: - description: Cache key suffix used to invalidate installer-based caches. - required: false - default: v1 - install-attempts: - description: Number of installer retry attempts. - required: false - default: "3" - allow-preinstalled: - description: Reuse an existing opencode found on PATH instead of forcing installer bootstrap. - required: false - default: "false" - version: - description: Minimum required OpenCode version (semver). Empty string disables version checking. - required: false - default: "1.4.5" - working-directory: - description: Optional working directory before running opencode. - required: false - default: "" - attempts: - description: Total number of attempts before failing. - required: false - default: "3" - retry-profile: - description: Built-in retry profile, defaults to github-network for common GitHub usage. - required: false - default: github-network - retry-on-regex: - description: Retry only when stderr or stdout matches this regex. - required: false - default: "" - retry-delay-seconds: - description: Base delay between retries in seconds. - required: false - default: "15" - timeout-seconds: - description: Maximum execution time in seconds for `opencode github run`. Set 0 to disable. - required: false - default: "600" - prompt: - description: Value exported as PROMPT before running `opencode github run`. Defaults to a combined prompt that runs all three checks. - required: false - default: | - Run all three PR checks on this pull request (read-only mode, DO NOT modify any code). - - Execute these checks in order and combine the results: - - ## Check 1: Code Review - Review the code for: - - Code quality issues - - Potential bugs or logic errors - - Code style consistency - - Security concerns - - Performance issues - - ## Check 2: Feature Missing - 1. Find linked issues via `gh pr view --json closingIssuesReferences,title,body`. - 2. If linked issues exist, read each issue body as the feature spec. - 3. If no linked issues, extract requirements from the PR title and body. - 4. Compare the spec/requirements against the implementation. - Focus on: missing features, partial implementations, integration gaps, missing edge case handling. - Do NOT report: code style issues, bugs in existing code, suggestions beyond the spec. - - ## Check 3: Regression Test Missing - 1. Determine PR type: BUGFIX / BEHAVIOR_CHANGE / NEW_FEATURE / CHORE. - 2. For BUGFIX and BEHAVIOR_CHANGE PRs, check if regression tests exist. - 3. For NEW_FEATURE and CHORE PRs, no regression tests needed. - Focus on: bug fixes without reproducers, behavior changes without coverage, missing edge case tests. - Do NOT report: code style, missing features from spec, test suggestions for trivial changes. - - Please respond in Chinese. DO NOT modify any code, only provide analysis. - - Output format - three sections: - - ### 代码审查 - - Decision: 可合并 / 有条件合并 / 不可合并 - - Summary - - 阻塞项 (blocking issues, or "阻塞项:无") - - 建议项 (suggestions, or "建议项:无") - - ### 功能遗漏 - - Decision: 无遗漏 / 发现遗漏 - - PR type detected - - If gaps found, list each by severity (CRITICAL / MEDIUM / LOW) - - ### 回归测试 - - Decision: 无需回归测试 / 缺少回归测试 - - PR type detected - - If tests missing, list each gap with suggested test case description and severity (CRITICAL / MEDIUM / LOW) - use-github-token: - description: Value exported as USE_GITHUB_TOKEN before running `opencode github run`. - required: false - default: "true" - github-token: - description: Value exported as GITHUB_TOKEN before running `opencode github run`. - required: false - default: "" - zhipu-api-key: - description: Value exported as ZHIPU_API_KEY before running `opencode github run`. - required: false - default: "" - opencode-go-api-key: - description: Value exported as OPENCODE_API_KEY before running `opencode github run`. The opencode-go provider shares this env var with OpenCode Zen. - required: false - default: "" - deepseek-api-key: - description: Value exported as DEEPSEEK_API_KEY before running `opencode github run`. - required: false - default: "" - fallback-models: - description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. - required: false - default: "" - model-timeout-seconds: - description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. - required: false - default: "300" - fallback-on-regex: - description: Rotate to the next fallback model when output matches this regex. - required: false - default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out - -runs: - using: composite - steps: - - if: ${{ runner.os != 'Linux' }} - shell: bash - run: | - set -euo pipefail - printf 'pr-checks currently supports Linux runners only\n' >&2 - exit 1 - - - id: paths - shell: bash - env: - INPUT_INSTALL_DIR: ${{ inputs.install-dir }} - INPUT_XDG_CACHE_HOME: ${{ inputs.xdg-cache-home }} - run: | - set -euo pipefail - install_dir="$INPUT_INSTALL_DIR" - xdg_cache_home="$INPUT_XDG_CACHE_HOME" - - if [[ -z "$install_dir" ]]; then - install_dir="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/bin" - fi - - if [[ -z "$xdg_cache_home" ]]; then - xdg_cache_home="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/cache" - fi - - printf 'install_dir=%s\n' "$install_dir" >>"$GITHUB_OUTPUT" - printf 'xdg_cache_home=%s\n' "$xdg_cache_home" >>"$GITHUB_OUTPUT" - - - id: key - shell: bash - env: - INPUT_INSTALL_URL: ${{ inputs.install-url }} - run: | - set -euo pipefail - install_url_hash="$(printf '%s' "$INPUT_INSTALL_URL" | sha256sum | cut -d ' ' -f1)" - printf 'install_url_hash=%s\n' "$install_url_hash" >>"$GITHUB_OUTPUT" - - - id: cache - if: ${{ inputs.cache == 'true' }} - uses: actions/cache@v5 - with: - path: | - ${{ steps.paths.outputs.install_dir }} - ${{ steps.paths.outputs.xdg_cache_home }} - key: pr-checks-opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ inputs.version }}-${{ inputs.cache-key }} - - - shell: bash - env: - OPENCODE_INSTALL_DIR: ${{ steps.paths.outputs.install_dir }} - XDG_CACHE_HOME: ${{ steps.paths.outputs.xdg_cache_home }} - OPENCODE_INSTALL_URL: ${{ inputs.install-url }} - OPENCODE_INSTALL_ATTEMPTS: ${{ inputs.install-attempts }} - OPENCODE_ALLOW_PREINSTALLED: ${{ inputs.allow-preinstalled }} - OPENCODE_MIN_VERSION: ${{ inputs.version }} - run: ${{ github.action_path }}/../setup-opencode/install-opencode.sh - - - shell: bash - env: - GITHUB_RUN_OPENCODE_WORKING_DIRECTORY: ${{ inputs.working-directory }} - GITHUB_RUN_OPENCODE_ATTEMPTS: ${{ inputs.attempts }} - GITHUB_RUN_OPENCODE_RETRY_PROFILE: ${{ inputs.retry-profile }} - GITHUB_RUN_OPENCODE_RETRY_ON_REGEX: ${{ inputs.retry-on-regex }} - GITHUB_RUN_OPENCODE_RETRY_DELAY_SECONDS: ${{ inputs.retry-delay-seconds }} - GITHUB_RUN_OPENCODE_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }} - GITHUB_RUN_OPENCODE_MODEL: ${{ inputs.model }} - GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} - GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} - GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} - GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} - GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} - GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} - GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} - GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} - GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} - run: | - if ! command -v python3 >/dev/null 2>&1; then - printf 'python3 is required but not installed on this runner\n' >&2 - exit 1 - fi - python3 ${{ github.action_path }}/../github-run-opencode/run-github-opencode.py diff --git a/regression-test-missing/action.yml b/regression-test-missing/action.yml index f359d8a..2251b6a 100644 --- a/regression-test-missing/action.yml +++ b/regression-test-missing/action.yml @@ -31,9 +31,9 @@ inputs: required: false default: "false" version: - description: Minimum required OpenCode version (semver). If the cached binary is older, it will be reinstalled. Empty string disables version checking. + description: Minimum required OpenCode version (semver). If the cached binary is older, it will be reinstalled. Defaults to the version in setup-opencode/default-version. Use "none" to disable version checking. required: false - default: "1.4.5" + default: "" working-directory: description: Optional working directory before running opencode. required: false @@ -152,6 +152,27 @@ runs: printf 'regression-test-missing currently supports Linux runners only\n' >&2 exit 1 + - id: version + shell: bash + run: | + set -euo pipefail + effective="${{ inputs.version }}" + if [[ "$effective" == "none" ]]; then + effective="" + elif [[ -z "$effective" ]]; then + default_file="${{ github.action_path }}/../setup-opencode/default-version" + if [[ ! -f "$default_file" ]]; then + printf 'error: default-version file not found at %s\n' "$default_file" >&2 + exit 1 + fi + effective="$(tr -d '[:space:]' < "$default_file")" + if [[ ! "$effective" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + printf 'error: invalid version in %s: %s\n' "$default_file" "$(cat "$default_file")" >&2 + exit 1 + fi + fi + printf 'version=%s\n' "$effective" >>"$GITHUB_OUTPUT" + - id: paths shell: bash env: @@ -189,7 +210,7 @@ runs: path: | ${{ steps.paths.outputs.install_dir }} ${{ steps.paths.outputs.xdg_cache_home }} - key: opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ inputs.version }}-${{ inputs.cache-key }} + key: regression-test-missing-opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ steps.version.outputs.version }}-${{ inputs.cache-key }} - shell: bash env: @@ -198,7 +219,7 @@ runs: OPENCODE_INSTALL_URL: ${{ inputs.install-url }} OPENCODE_INSTALL_ATTEMPTS: ${{ inputs.install-attempts }} OPENCODE_ALLOW_PREINSTALLED: ${{ inputs.allow-preinstalled }} - OPENCODE_MIN_VERSION: ${{ inputs.version }} + OPENCODE_MIN_VERSION: ${{ steps.version.outputs.version }} run: ${{ github.action_path }}/../setup-opencode/install-opencode.sh - shell: bash From f9aac80ee688be11cfad53854a7ebc5fa434d6f5 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 30 Apr 2026 10:01:42 +0800 Subject: [PATCH 03/11] fix: use local action references in workflow files Workflow files referenced Svtter/opencode-actions/regression-test-missing@v2 which does not exist yet in the v2 tag. Use ./ local paths instead so they resolve from the PR branch during testing. --- .github/workflows/pr-checks.yml | 6 +++--- .github/workflows/regression-test-missing.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index dd8b550..f5dba3b 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -21,7 +21,7 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} - name: Run OpenCode review - uses: Svtter/opencode-actions/review@v2 + uses: ./review with: model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -48,7 +48,7 @@ jobs: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} - name: Run feature missing audit - uses: Svtter/opencode-actions/feature-missing@v2 + uses: ./feature-missing with: model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} @@ -75,7 +75,7 @@ jobs: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} - name: Run regression test missing audit - uses: Svtter/opencode-actions/regression-test-missing@v2 + uses: ./regression-test-missing with: model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index 8b474be..d942e81 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -24,7 +24,7 @@ jobs: ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} - name: Run regression test missing audit - uses: Svtter/opencode-actions/regression-test-missing@v2 + uses: ./regression-test-missing with: model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} From 727ee27fa4e81059a89f4b99ed6c80ea03ca811b Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 30 Apr 2026 10:08:58 +0800 Subject: [PATCH 04/11] fix: address remaining review feedback - Create pr-checks/action.yml composite action with combined prompt for single-invocation usage (users can reference via Svtter/opencode-actions/pr-checks@v2) - Keep .github/workflows/pr-checks.yml as parallel 3-job version - Add fork protection to regression-test-missing.yml - Unify checkout step names in pr-checks.yml - Add pr-checks smoke test to ci.yml --- .github/workflows/ci.yml | 10 + .github/workflows/pr-checks.yml | 6 +- .github/workflows/regression-test-missing.yml | 2 +- pr-checks/action.yml | 251 ++++++++++++++++++ 4 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 pr-checks/action.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51b1e45..d78cf88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,16 @@ jobs: github-token: smoke-gh-token zhipu-api-key: smoke-zhipu-token + - name: Run pr-checks action + uses: ./pr-checks + with: + install-url: http://127.0.0.1:8765/fake-installer.sh + cache: false + install-attempts: 1 + attempts: 1 + github-token: smoke-gh-token + zhipu-api-key: smoke-zhipu-token + - name: Stop fake installer server if: always() run: | diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index f5dba3b..51eeb86 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -39,7 +39,8 @@ jobs: pull-requests: write issues: write steps: - - uses: actions/checkout@v6 + - name: Checkout PR head + uses: actions/checkout@v6 with: clean: true persist-credentials: true @@ -66,7 +67,8 @@ jobs: pull-requests: write issues: write steps: - - uses: actions/checkout@v6 + - name: Checkout PR head + uses: actions/checkout@v6 with: clean: true persist-credentials: true diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index d942e81..5bf50df 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -7,7 +7,7 @@ on: jobs: regression-test-missing: name: Regression Test Missing - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: id-token: write diff --git a/pr-checks/action.yml b/pr-checks/action.yml new file mode 100644 index 0000000..6d219aa --- /dev/null +++ b/pr-checks/action.yml @@ -0,0 +1,251 @@ +name: OpenCode PR Checks +description: Runs review, feature-missing, and regression-test-missing checks in a single opencode invocation with a combined prompt. + +inputs: + model: + description: Value exported as MODEL before running `opencode github run`. + required: false + default: "" + install-url: + description: Installer URL used to bootstrap OpenCode. + required: false + default: https://opencode.ai/install + install-dir: + description: Directory where the opencode binary should be installed. + required: false + default: "" + xdg-cache-home: + description: Dedicated XDG cache directory for OpenCode. + required: false + default: "" + cache: + description: Cache the install and XDG cache directories with actions/cache. + required: false + default: "true" + cache-key: + description: Cache key suffix used to invalidate installer-based caches. + required: false + default: v1 + install-attempts: + description: Number of installer retry attempts. + required: false + default: "3" + allow-preinstalled: + description: Reuse an existing opencode found on PATH instead of forcing installer bootstrap. + required: false + default: "false" + version: + description: Minimum required OpenCode version (semver). Defaults to the version in setup-opencode/default-version. Use "none" to disable version checking. + required: false + default: "" + working-directory: + description: Optional working directory before running opencode. + required: false + default: "" + attempts: + description: Total number of attempts before failing. + required: false + default: "3" + retry-profile: + description: Built-in retry profile, defaults to github-network for common GitHub usage. + required: false + default: github-network + retry-on-regex: + description: Retry only when stderr or stdout matches this regex. + required: false + default: "" + retry-delay-seconds: + description: Base delay between retries in seconds. + required: false + default: "15" + timeout-seconds: + description: Maximum execution time in seconds for `opencode github run`. Set 0 to disable. + required: false + default: "600" + fallback-models: + description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. + required: false + default: "" + model-timeout-seconds: + description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. + required: false + default: "300" + fallback-on-regex: + description: Rotate to the next fallback model when output matches this regex. + required: false + default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out + prompt: + description: Value exported as PROMPT before running `opencode github run`. Defaults to a combined prompt that runs all three checks. + required: false + default: | + Run all three PR checks on this pull request (read-only mode, DO NOT modify any code). + + Execute these checks in order and combine the results: + + ## Check 1: Code Review + Review the code for: + - Code quality issues + - Potential bugs or logic errors + - Code style consistency + - Security concerns + - Performance issues + + ## Check 2: Feature Missing + 1. Find linked issues via `gh pr view --json closingIssuesReferences,title,body`. + 2. If linked issues exist, read each issue body as the feature spec. + 3. If no linked issues, extract requirements from the PR title and body. + 4. Compare the spec/requirements against the implementation. + Focus on: missing features, partial implementations, integration gaps, missing edge case handling. + Do NOT report: code style issues, bugs in existing code, suggestions beyond the spec. + + ## Check 3: Regression Test Missing + 1. Determine PR type: BUGFIX / BEHAVIOR_CHANGE / NEW_FEATURE / CHORE. + 2. For BUGFIX and BEHAVIOR_CHANGE PRs, check if regression tests exist. + 3. For NEW_FEATURE and CHORE PRs, no regression tests needed. + Focus on: bug fixes without reproducers, behavior changes without coverage, missing edge case tests. + Do NOT report: code style, missing features from spec, test suggestions for trivial changes. + + Please respond in Chinese. DO NOT modify any code, only provide analysis. + + Output format - three sections: + + ### 代码审查 + - Decision: 可合并 / 有条件合并 / 不可合并 + - Summary + - 阻塞项 (blocking issues, or "阻塞项:无") + - 建议项 (suggestions, or "建议项:无") + + ### 功能遗漏 + - Decision: 无遗漏 / 发现遗漏 + - PR type detected + - If gaps found, list each by severity (CRITICAL / MEDIUM / LOW) + + ### 回归测试 + - Decision: 无需回归测试 / 缺少回归测试 + - PR type detected + - If tests missing, list each gap with suggested test case description and severity (CRITICAL / MEDIUM / LOW) + use-github-token: + description: Value exported as USE_GITHUB_TOKEN before running `opencode github run`. + required: false + default: "true" + github-token: + description: Value exported as GITHUB_TOKEN before running `opencode github run`. + required: false + default: "" + zhipu-api-key: + description: Value exported as ZHIPU_API_KEY before running `opencode github run`. + required: false + default: "" + opencode-go-api-key: + description: Value exported as OPENCODE_API_KEY before running `opencode github run`. The opencode-go provider shares this env var with OpenCode Zen. + required: false + default: "" + deepseek-api-key: + description: Value exported as DEEPSEEK_API_KEY before running `opencode github run`. + required: false + default: "" + +runs: + using: composite + steps: + - if: ${{ runner.os != 'Linux' }} + shell: bash + run: | + set -euo pipefail + printf 'pr-checks currently supports Linux runners only\n' >&2 + exit 1 + + - id: version + shell: bash + run: | + set -euo pipefail + effective="${{ inputs.version }}" + if [[ "$effective" == "none" ]]; then + effective="" + elif [[ -z "$effective" ]]; then + default_file="${{ github.action_path }}/../setup-opencode/default-version" + if [[ ! -f "$default_file" ]]; then + printf 'error: default-version file not found at %s\n' "$default_file" >&2 + exit 1 + fi + effective="$(tr -d '[:space:]' < "$default_file")" + if [[ ! "$effective" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + printf 'error: invalid version in %s: %s\n' "$default_file" "$(cat "$default_file")" >&2 + exit 1 + fi + fi + printf 'version=%s\n' "$effective" >>"$GITHUB_OUTPUT" + + - id: paths + shell: bash + env: + INPUT_INSTALL_DIR: ${{ inputs.install-dir }} + INPUT_XDG_CACHE_HOME: ${{ inputs.xdg-cache-home }} + run: | + set -euo pipefail + install_dir="$INPUT_INSTALL_DIR" + xdg_cache_home="$INPUT_XDG_CACHE_HOME" + + if [[ -z "$install_dir" ]]; then + install_dir="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/bin" + fi + + if [[ -z "$xdg_cache_home" ]]; then + xdg_cache_home="${RUNNER_TOOL_CACHE:-$HOME/.cache}/opencode/cache" + fi + + printf 'install_dir=%s\n' "$install_dir" >>"$GITHUB_OUTPUT" + printf 'xdg_cache_home=%s\n' "$xdg_cache_home" >>"$GITHUB_OUTPUT" + + - id: key + shell: bash + env: + INPUT_INSTALL_URL: ${{ inputs.install-url }} + run: | + set -euo pipefail + install_url_hash="$(printf '%s' "$INPUT_INSTALL_URL" | sha256sum | cut -d ' ' -f1)" + printf 'install_url_hash=%s\n' "$install_url_hash" >>"$GITHUB_OUTPUT" + + - id: cache + if: ${{ inputs.cache == 'true' }} + uses: actions/cache@v5 + with: + path: | + ${{ steps.paths.outputs.install_dir }} + ${{ steps.paths.outputs.xdg_cache_home }} + key: pr-checks-opencode-${{ runner.os }}-${{ runner.arch }}-${{ steps.key.outputs.install_url_hash }}-${{ steps.version.outputs.version }}-${{ inputs.cache-key }} + + - shell: bash + env: + OPENCODE_INSTALL_DIR: ${{ steps.paths.outputs.install_dir }} + XDG_CACHE_HOME: ${{ steps.paths.outputs.xdg_cache_home }} + OPENCODE_INSTALL_URL: ${{ inputs.install-url }} + OPENCODE_INSTALL_ATTEMPTS: ${{ inputs.install-attempts }} + OPENCODE_ALLOW_PREINSTALLED: ${{ inputs.allow-preinstalled }} + OPENCODE_MIN_VERSION: ${{ steps.version.outputs.version }} + run: ${{ github.action_path }}/../setup-opencode/install-opencode.sh + + - shell: bash + env: + GITHUB_RUN_OPENCODE_WORKING_DIRECTORY: ${{ inputs.working-directory }} + GITHUB_RUN_OPENCODE_ATTEMPTS: ${{ inputs.attempts }} + GITHUB_RUN_OPENCODE_RETRY_PROFILE: ${{ inputs.retry-profile }} + GITHUB_RUN_OPENCODE_RETRY_ON_REGEX: ${{ inputs.retry-on-regex }} + GITHUB_RUN_OPENCODE_RETRY_DELAY_SECONDS: ${{ inputs.retry-delay-seconds }} + GITHUB_RUN_OPENCODE_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }} + GITHUB_RUN_OPENCODE_MODEL: ${{ inputs.model }} + GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} + GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} + GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} + GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} + GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} + GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} + GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} + GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} + GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} + run: | + if ! command -v python3 >/dev/null 2>&1; then + printf 'python3 is required but not installed on this runner\n' >&2 + exit 1 + fi + python3 ${{ github.action_path }}/../github-run-opencode/run-github-opencode.py From 588cef29d0f597616246a083f28375f0cc1fe9f3 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:09:52 +0800 Subject: [PATCH 05/11] fix: implement Plan C cache sharing, unify input order, add pr-checks to README - Add setup job in pr-checks.yml that installs and caches opencode first - Check jobs restore shared cache via setup-opencode, run composite actions with cache: false to avoid duplicate downloads - Move fallback-models/model-timeout-seconds/fallback-on-regex after model and before prompt in regression-test-missing/action.yml (matches review/action.yml order) - Move model after timeout-seconds in pr-checks/action.yml (matches review/action.yml order) - Add pr-checks to README Usage section --- .github/workflows/pr-checks.yml | 54 ++++++++++++++++++++++++++++-- README.md | 1 + pr-checks/action.yml | 8 ++--- regression-test-missing/action.yml | 24 ++++++------- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 51eeb86..285af9e 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -5,9 +5,27 @@ on: types: [opened, synchronize, reopened, ready_for_review] jobs: + setup: + name: Setup OpenCode + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + outputs: + install-dir: ${{ steps.setup.outputs.install-dir }} + xdg-cache-home: ${{ steps.setup.outputs.xdg-cache-home }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup OpenCode + id: setup + uses: ./setup-opencode + review: name: Code Review - if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + needs: [setup] runs-on: ubuntu-latest permissions: contents: read @@ -20,9 +38,19 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} + - name: Restore OpenCode cache + uses: ./setup-opencode + with: + cache: true + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Run OpenCode review uses: ./review with: + cache: false + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} @@ -31,7 +59,7 @@ jobs: feature-missing: name: Feature Missing - if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + needs: [setup] runs-on: ubuntu-latest permissions: id-token: write @@ -48,9 +76,19 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + - name: Restore OpenCode cache + uses: ./setup-opencode + with: + cache: true + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Run feature missing audit uses: ./feature-missing with: + cache: false + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} @@ -59,7 +97,7 @@ jobs: regression-test-missing: name: Regression Test Missing - if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + needs: [setup] runs-on: ubuntu-latest permissions: id-token: write @@ -76,9 +114,19 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + - name: Restore OpenCode cache + uses: ./setup-opencode + with: + cache: true + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Run regression test missing audit uses: ./regression-test-missing with: + cache: false + install-dir: ${{ needs.setup.outputs.install-dir }} + xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} model: ${{ vars.MODEL_NAME }} github-token: ${{ secrets.GITHUB_TOKEN }} zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }} diff --git a/README.md b/README.md index c9dd7fe..72306e2 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ uses: Svtter/opencode-actions/review@v2 uses: Svtter/opencode-actions/feature-missing@v2 uses: Svtter/opencode-actions/spec-coverage@v2 uses: Svtter/opencode-actions/regression-test-missing@v2 +uses: Svtter/opencode-actions/pr-checks@v2 uses: Svtter/opencode-actions/github-run-opencode@v2 uses: Svtter/opencode-actions/setup-opencode@v2 uses: Svtter/opencode-actions/run-opencode@v2 diff --git a/pr-checks/action.yml b/pr-checks/action.yml index 6d219aa..5af38c7 100644 --- a/pr-checks/action.yml +++ b/pr-checks/action.yml @@ -2,10 +2,6 @@ name: OpenCode PR Checks description: Runs review, feature-missing, and regression-test-missing checks in a single opencode invocation with a combined prompt. inputs: - model: - description: Value exported as MODEL before running `opencode github run`. - required: false - default: "" install-url: description: Installer URL used to bootstrap OpenCode. required: false @@ -62,6 +58,10 @@ inputs: description: Maximum execution time in seconds for `opencode github run`. Set 0 to disable. required: false default: "600" + model: + description: Value exported as MODEL before running `opencode github run`. + required: false + default: "" fallback-models: description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. required: false diff --git a/regression-test-missing/action.yml b/regression-test-missing/action.yml index 2251b6a..791918a 100644 --- a/regression-test-missing/action.yml +++ b/regression-test-missing/action.yml @@ -62,6 +62,18 @@ inputs: description: Value exported as MODEL before running `opencode github run`. required: false default: "" + fallback-models: + description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. + required: false + default: "" + model-timeout-seconds: + description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. + required: false + default: "300" + fallback-on-regex: + description: Rotate to the next fallback model when output matches this regex. + required: false + default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out prompt: description: Value exported as PROMPT before running `opencode github run`. required: false @@ -129,18 +141,6 @@ inputs: description: Value exported as DEEPSEEK_API_KEY before running `opencode github run`. required: false default: "" - fallback-models: - description: Optional comma- or newline-delimited fallback models tried after timeout or timeout-like failures. - required: false - default: "" - model-timeout-seconds: - description: Per-model timeout used before rotating to the next fallback model when fallback-models are configured. Set 0 to disable. - required: false - default: "300" - fallback-on-regex: - description: Rotate to the next fallback model when output matches this regex. - required: false - default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out runs: using: composite From b443a5e51f5993dba8993fc104ec5d98c1d6c601 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:18:01 +0800 Subject: [PATCH 06/11] fix: add git identity config to pr-checks parallel jobs The opencode tool attempts git commit for review results but fails with 'Author identity unknown' because the runner has no git user.name configured. Add explicit git config step to all three parallel jobs. --- .github/workflows/pr-checks.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 285af9e..40d241d 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -45,6 +45,11 @@ jobs: install-dir: ${{ needs.setup.outputs.install-dir }} xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Run OpenCode review uses: ./review with: @@ -83,6 +88,11 @@ jobs: install-dir: ${{ needs.setup.outputs.install-dir }} xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Run feature missing audit uses: ./feature-missing with: @@ -121,6 +131,11 @@ jobs: install-dir: ${{ needs.setup.outputs.install-dir }} xdg-cache-home: ${{ needs.setup.outputs.xdg-cache-home }} + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Run regression test missing audit uses: ./regression-test-missing with: From c68390c10d8317fb1b3715af1f99e4d2c7793d15 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:28:42 +0800 Subject: [PATCH 07/11] fix: add missing inputs, OPENCODE_PERMISSION, and fix README CI description - Add reasoning-effort, enable-thinking inputs to regression-test-missing and pr-checks actions - Add extra-env input to pr-checks action (parity with review action) - Add GITHUB_RUN_OPENCODE_PERMISSION deny policy to prevent unwanted mutations - Fix README CI description to accurately list smoke-tested actions - Add name to checkout step in regression-test-missing.yml --- .github/workflows/regression-test-missing.yml | 3 ++- README.md | 2 +- pr-checks/action.yml | 26 ++++++++++++++----- regression-test-missing/action.yml | 12 +++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index 5bf50df..8f5b290 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -15,7 +15,8 @@ jobs: pull-requests: write issues: write steps: - - uses: actions/checkout@v6 + - name: Checkout PR head + uses: actions/checkout@v6 with: clean: true persist-credentials: true diff --git a/README.md b/README.md index 72306e2..de9db05 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ This repository includes a CI workflow that: - runs `shellcheck` on every bundled shell script - runs the local shell-based regression suite -- smoke-tests all actions through `uses: ./setup-opencode`, `uses: ./run-opencode`, `uses: ./github-run-opencode`, `uses: ./review`, `uses: ./feature-missing`, `uses: ./spec-coverage`, and `uses: ./regression-test-missing` +- smoke-tests all actions through `uses: ./setup-opencode`, `uses: ./run-opencode`, `uses: ./github-run-opencode`, `uses: ./review`, `uses: ./regression-test-missing`, and `uses: ./pr-checks` ## Release Policy diff --git a/pr-checks/action.yml b/pr-checks/action.yml index 5af38c7..796c1ae 100644 --- a/pr-checks/action.yml +++ b/pr-checks/action.yml @@ -74,6 +74,21 @@ inputs: description: Rotate to the next fallback model when output matches this regex. required: false default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out + reasoning-effort: + description: Reasoning effort level for the model agent. Allowed values are low, medium, high, max. + required: false + default: "max" + enable-thinking: + description: Enable thinking mode for the model agent. When true, generates opencode.json with thinking.type=enabled. + required: false + default: "true" + extra-env: + description: >- + Extra environment variables to pass to opencode runtime. + Multi-line KEY=VALUE pairs, one per line. Empty lines and lines + starting with '#' are ignored. + required: false + default: "" prompt: description: Value exported as PROMPT before running `opencode github run`. Defaults to a combined prompt that runs all three checks. required: false @@ -237,12 +252,11 @@ runs: GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} - GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} - GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} - GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} - GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} - GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} - GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} + GITHUB_RUN_OPENCODE_REASONING_EFFORT: ${{ inputs.reasoning-effort }} + GITHUB_RUN_OPENCODE_ENABLE_THINKING: ${{ inputs.enable-thinking }} + GITHUB_RUN_OPENCODE_EXTRA_ENV: ${{ inputs.extra-env }} + GITHUB_RUN_OPENCODE_PERMISSION: >- + {"edit":"deny","bash":{"git commit *":"deny","git push *":"deny","git add *":"deny","git stash *":"deny","git reset *":"deny","git checkout *":"deny"}} run: | if ! command -v python3 >/dev/null 2>&1; then printf 'python3 is required but not installed on this runner\n' >&2 diff --git a/regression-test-missing/action.yml b/regression-test-missing/action.yml index 791918a..455898e 100644 --- a/regression-test-missing/action.yml +++ b/regression-test-missing/action.yml @@ -74,6 +74,14 @@ inputs: description: Rotate to the next fallback model when output matches this regex. required: false default: timed out|timeout|deadline exceeded|context deadline exceeded|operation timed out|connection timed out + reasoning-effort: + description: Reasoning effort level for the model agent. Allowed values are low, medium, high, max. + required: false + default: "max" + enable-thinking: + description: Enable thinking mode for the model agent. When true, generates opencode.json with thinking.type=enabled. + required: false + default: "true" prompt: description: Value exported as PROMPT before running `opencode github run`. required: false @@ -237,9 +245,13 @@ runs: GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} + GITHUB_RUN_OPENCODE_REASONING_EFFORT: ${{ inputs.reasoning-effort }} + GITHUB_RUN_OPENCODE_ENABLE_THINKING: ${{ inputs.enable-thinking }} GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} + GITHUB_RUN_OPENCODE_PERMISSION: >- + {"edit":"deny","bash":{"git commit *":"deny","git push *":"deny","git add *":"deny","git stash *":"deny","git reset *":"deny","git checkout *":"deny"}} run: | if ! command -v python3 >/dev/null 2>&1; then printf 'python3 is required but not installed on this runner\n' >&2 From a23aab85e80d0bc31f2f6877ea8fb8d926832b50 Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:35:07 +0800 Subject: [PATCH 08/11] fix: restore env var pass-throughs in pr-checks and add git identity to regression-test-missing workflow - pr-checks/action.yml: add 6 missing env vars (PROMPT, USE_GITHUB_TOKEN, GITHUB_TOKEN, ZHIPU_API_KEY, OPENCODE_GO_API_KEY, DEEPSEEK_API_KEY) that were accidentally omitted from the runs env block - regression-test-missing.yml: add git config step before running the action to prevent 'Author identity unknown' failure --- .github/workflows/regression-test-missing.yml | 5 +++++ pr-checks/action.yml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index 8f5b290..821a230 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -24,6 +24,11 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref || github.event.pull_request.head.sha }} + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Run regression test missing audit uses: ./regression-test-missing with: diff --git a/pr-checks/action.yml b/pr-checks/action.yml index 796c1ae..1f3a91e 100644 --- a/pr-checks/action.yml +++ b/pr-checks/action.yml @@ -249,6 +249,12 @@ runs: GITHUB_RUN_OPENCODE_RETRY_DELAY_SECONDS: ${{ inputs.retry-delay-seconds }} GITHUB_RUN_OPENCODE_TIMEOUT_SECONDS: ${{ inputs.timeout-seconds }} GITHUB_RUN_OPENCODE_MODEL: ${{ inputs.model }} + GITHUB_RUN_OPENCODE_PROMPT: ${{ inputs.prompt }} + GITHUB_RUN_OPENCODE_USE_GITHUB_TOKEN: ${{ inputs.use-github-token }} + GITHUB_RUN_OPENCODE_GITHUB_TOKEN: ${{ inputs.github-token }} + GITHUB_RUN_OPENCODE_ZHIPU_API_KEY: ${{ inputs.zhipu-api-key }} + GITHUB_RUN_OPENCODE_OPENCODE_GO_API_KEY: ${{ inputs.opencode-go-api-key }} + GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} From 79fe4f60849d25e5565f749294d81fdfeacb721a Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:41:59 +0800 Subject: [PATCH 09/11] fix: add reasoning-effort validation and document git identity intent --- .github/workflows/regression-test-missing.yml | 1 + github-run-opencode/run-github-opencode.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index 821a230..30b33a0 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -28,6 +28,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + # Defense-in-depth: identity configured even though permission policy denies git ops - name: Run regression test missing audit uses: ./regression-test-missing diff --git a/github-run-opencode/run-github-opencode.py b/github-run-opencode/run-github-opencode.py index d12166d..5124658 100755 --- a/github-run-opencode/run-github-opencode.py +++ b/github-run-opencode/run-github-opencode.py @@ -219,6 +219,9 @@ def main() -> int: os.environ[key] = value reasoning_effort = get_env("GITHUB_RUN_OPENCODE_REASONING_EFFORT", "") + if reasoning_effort and reasoning_effort not in ("low", "medium", "high", "max"): + print(f"reasoning-effort must be one of low, medium, high, max; got '{reasoning_effort}'", file=sys.stderr) + sys.exit(1) enable_thinking = get_env("GITHUB_RUN_OPENCODE_ENABLE_THINKING", "false") working_directory = get_env("GITHUB_RUN_OPENCODE_WORKING_DIRECTORY", "") From b3dea2cf36e8b3b9dc63819caa17bb541729bfbf Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:48:06 +0800 Subject: [PATCH 10/11] fix: sync smoke-test.yml with new actions and add extra-env to regression-test-missing - Add regression-test-missing and pr-checks steps to smoke-test.yml - Add extra-env input to regression-test-missing/action.yml for consistency with pr-checks - Improve git identity comment in regression-test-missing workflow --- .github/workflows/regression-test-missing.yml | 4 +++- .github/workflows/smoke-test.yml | 18 ++++++++++++++++++ regression-test-missing/action.yml | 8 ++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/regression-test-missing.yml b/.github/workflows/regression-test-missing.yml index 30b33a0..90234bc 100644 --- a/.github/workflows/regression-test-missing.yml +++ b/.github/workflows/regression-test-missing.yml @@ -28,7 +28,9 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - # Defense-in-depth: identity configured even though permission policy denies git ops + # Configure git identity as defense-in-depth; some tools may implicitly + # invoke git requiring user identity even though the permission policy + # denies write operations. - name: Run regression test missing audit uses: ./regression-test-missing diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 10d388d..30684b8 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -36,3 +36,21 @@ jobs: attempts: "1" github-token: ${{ secrets.GITHUB_TOKEN }} continue-on-error: true + + - name: Test regression-test-missing action loads + uses: ./regression-test-missing + with: + model: test-model + timeout-seconds: "5" + attempts: "1" + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + + - name: Test pr-checks action loads + uses: ./pr-checks + with: + model: test-model + timeout-seconds: "5" + attempts: "1" + github-token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true diff --git a/regression-test-missing/action.yml b/regression-test-missing/action.yml index 455898e..fbb6bb5 100644 --- a/regression-test-missing/action.yml +++ b/regression-test-missing/action.yml @@ -82,6 +82,13 @@ inputs: description: Enable thinking mode for the model agent. When true, generates opencode.json with thinking.type=enabled. required: false default: "true" + extra-env: + description: >- + Extra environment variables to pass to opencode runtime. + Multi-line KEY=VALUE pairs, one per line. Empty lines and lines + starting with '#' are ignored. + required: false + default: "" prompt: description: Value exported as PROMPT before running `opencode github run`. required: false @@ -247,6 +254,7 @@ runs: GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY: ${{ inputs.deepseek-api-key }} GITHUB_RUN_OPENCODE_REASONING_EFFORT: ${{ inputs.reasoning-effort }} GITHUB_RUN_OPENCODE_ENABLE_THINKING: ${{ inputs.enable-thinking }} + GITHUB_RUN_OPENCODE_EXTRA_ENV: ${{ inputs.extra-env }} GITHUB_RUN_OPENCODE_FALLBACK_MODELS: ${{ inputs.fallback-models }} GITHUB_RUN_OPENCODE_MODEL_TIMEOUT_SECONDS: ${{ inputs.model-timeout-seconds }} GITHUB_RUN_OPENCODE_FALLBACK_ON_REGEX: ${{ inputs.fallback-on-regex }} From 56420b7f8ab00fa0ea5a3aa4be463a8349f9822e Mon Sep 17 00:00:00 2001 From: svtter Date: Thu, 14 May 2026 23:54:01 +0800 Subject: [PATCH 11/11] fix: correct feature-missing description in README and add permission type check - Fix copy-paste error: feature-missing description changed from 'spec coverage audit' to 'PR scope audit' - Add pr-checks@v2 to usage guide - Add type validation for GITHUB_RUN_OPENCODE_PERMISSION JSON input --- README.md | 2 +- github-run-opencode/run-github-opencode.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de9db05..6bb8cf3 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ This repository includes a CI workflow that: 2. Verify `CI` passes on `main`. 3. Create a GitHub release with a semver tag such as `v1.0.0`. 4. Confirm the `Update Major Tag` workflow moved `v1` to that release. -5. Use `owner/repo/review@v2` for the simplest review setup, `owner/repo/feature-missing@v2` for spec coverage audit, `owner/repo/spec-coverage@v2` for spec coverage audit, `owner/repo/regression-test-missing@v2` for regression test audit, `owner/repo/github-run-opencode@v2` for generic `github run`, or `owner/repo/setup-opencode@v2` plus `owner/repo/run-opencode@v2` for more control. +5. Use `owner/repo/review@v2` for the simplest review setup, `owner/repo/feature-missing@v2` for PR scope audit, `owner/repo/spec-coverage@v2` for spec coverage audit, `owner/repo/regression-test-missing@v2` for regression test audit, `owner/repo/pr-checks@v2` for combined PR checks, `owner/repo/github-run-opencode@v2` for generic `github run`, or `owner/repo/setup-opencode@v2` plus `owner/repo/run-opencode@v2` for more control. The initial release-notes template lives at `docs/releases/v1.0.0.md`. diff --git a/github-run-opencode/run-github-opencode.py b/github-run-opencode/run-github-opencode.py index 5124658..abb9d46 100755 --- a/github-run-opencode/run-github-opencode.py +++ b/github-run-opencode/run-github-opencode.py @@ -233,6 +233,9 @@ def main() -> int: except json.JSONDecodeError: print(f"Invalid JSON in GITHUB_RUN_OPENCODE_PERMISSION: {permission_raw}", file=sys.stderr) sys.exit(1) + if not isinstance(permission, dict): + print("GITHUB_RUN_OPENCODE_PERMISSION must be a JSON object", file=sys.stderr) + sys.exit(1) needs_config = reasoning_effort or enable_thinking.lower() == "true" or permission if needs_config: