Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
149 changes: 149 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: OpenCode PR Checks

on:
pull_request:
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
needs: [setup]
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: 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: 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:
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 }}
deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }}
opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }}

feature-missing:
name: Feature Missing
needs: [setup]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout PR head
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: 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: 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:
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 }}
deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }}
opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }}

regression-test-missing:
name: Regression Test Missing
needs: [setup]
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
issues: write
steps:
- name: Checkout PR head
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: 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: 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:
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 }}
deepseek-api-key: ${{ secrets.DEEPSEEK_API_KEY }}
opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }}
42 changes: 42 additions & 0 deletions .github/workflows/regression-test-missing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 && 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:
- name: Checkout PR head
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: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# 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
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 }}
18 changes: 18 additions & 0 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -185,6 +201,8 @@ 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/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
Expand Down Expand Up @@ -219,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`, and `uses: ./spec-coverage`
- 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

Expand All @@ -234,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 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 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`.

Expand Down
6 changes: 6 additions & 0 deletions github-run-opencode/run-github-opencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "")

Expand All @@ -230,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:
Expand Down
Loading
Loading