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
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ jobs:
github-token: smoke-gh-token
zhipu-api-key: smoke-zhipu-token

- name: Run test-value-detector convenience action
uses: ./test-value-detector
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
9 changes: 9 additions & 0 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,12 @@ jobs:
attempts: "1"
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true

- name: Test test-value-detector action loads
uses: ./test-value-detector
with:
model: test-model
timeout-seconds: "5"
attempts: "1"
github-token: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
34 changes: 31 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
- `test-value-detector`: detects low-value tests in PRs — empty assertions, hardcoded mocks, detached tests, duplicates, missing edge-case coverage
- `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 @@ -124,13 +125,39 @@ Unlike `feature-missing` (which checks PR self-described scope), `spec-coverage`
opencode-go-api-key: ${{ secrets.OPENCODE_GO_API_KEY }}
```

### How the three review actions differ
## test-value-detector

Use this to automatically detect low-value tests in pull requests — tests that pass CI but contribute nothing to code quality.

- identifies empty assertions and always-true conditions
- flags hardcoded mocks that decouple tests from real logic
- detects tests referencing non-existent or mismatched functions
- spots duplicate tests with no additional coverage value
- highlights missing boundary, error, and edge-case coverage
- classifies findings by severity: CRITICAL, MEDIUM, LOW
- shares the same inputs and cache as `review`/`github-run-opencode`

```yaml
- name: Run test value detection
uses: Svtter/opencode-actions/test-value-detector@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 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 |
| `test-value-detector` | PR test code | Low-value test patterns (empty assertions, hardcoded mocks, detached tests, duplicates, missing edge-case coverage) |

### Customization

Override the built-in prompt via the `prompt` input to adjust detection focus or severity thresholds for your project.

## setup-opencode

Expand Down Expand Up @@ -185,6 +212,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/test-value-detector@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 +247,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: ./test-value-detector`

## Release Policy

Expand All @@ -234,7 +262,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/test-value-detector@v2` for low-value test detection, `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
9 changes: 9 additions & 0 deletions feature-missing/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,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
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. Values may contain '=' and '#'
characters; only lines starting with '#' are treated as comments.
required: false
default: ""

runs:
using: composite
Expand Down Expand Up @@ -253,6 +261,7 @@ 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_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: |
Expand Down
23 changes: 20 additions & 3 deletions github-run-opencode/run-github-opencode.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ def main() -> int:
set_env("DEEPSEEK_API_KEY", get_env("GITHUB_RUN_OPENCODE_DEEPSEEK_API_KEY"))

# Extra env vars from extra-env input
BLOCKED_ENV_KEYS = frozenset({
"PATH", "HOME", "USER", "SHELL", "MODEL", "GITHUB_TOKEN",
"GITHUB_WORKSPACE", "GITHUB_EVENT_PATH", "GITHUB_SHA",
"GITHUB_REPOSITORY", "GITHUB_REF", "GITHUB_RUN_ID",
"GITHUB_ACTIONS", "LD_LIBRARY_PATH", "PYTHONPATH",
})
extra_env_raw = get_env("GITHUB_RUN_OPENCODE_EXTRA_ENV")
if extra_env_raw:
for line in extra_env_raw.splitlines():
Expand All @@ -214,9 +220,17 @@ def main() -> int:
continue
key, _, value = line.partition("=")
key = key.strip()
value = value.strip()
if key:
os.environ[key] = value
if not key:
continue
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', key):
print(f"::error::extra-env key '{key}' is not a valid environment variable name (must match [A-Za-z_][A-Za-z0-9_]*)", file=sys.stderr)
sys.exit(1)
if key in BLOCKED_ENV_KEYS:
print(f"::error::extra-env key '{key}' is blocked — overriding this variable is not allowed", file=sys.stderr)
sys.exit(1)
if re.search(r'(API_KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)', key, re.IGNORECASE):
print(f"::warning::extra-env key '{key}' looks like a sensitive variable — make sure this is intentional")
os.environ[key] = value.strip()

reasoning_effort = get_env("GITHUB_RUN_OPENCODE_REASONING_EFFORT", "")
enable_thinking = get_env("GITHUB_RUN_OPENCODE_ENABLE_THINKING", "false")
Expand All @@ -230,6 +244,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(f"GITHUB_RUN_OPENCODE_PERMISSION must be a JSON object, got {type(permission).__name__}", file=sys.stderr)
sys.exit(1)

needs_config = reasoning_effort or enable_thinking.lower() == "true" or permission
if needs_config:
Expand Down
Loading