mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 08:52:21 +00:00
chore: add ClawHub autoreview skill (#2294)
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
---
|
||||
name: autoreview
|
||||
description: "Use when ClawHub needs Codex review, autoreview, second-model review, or a final advisory review gate before commit, PR update, ship, or maintainer handoff."
|
||||
---
|
||||
|
||||
# Autoreview
|
||||
|
||||
Run Codex's built-in code review as a closeout check. This is code review
|
||||
(`codex review`), not Guardian `auto_review` approval routing.
|
||||
|
||||
Codex native review mode performs best and is recommended. Non-Codex reviewers
|
||||
are fallback or second-opinion paths that receive a generated diff prompt, not
|
||||
the full Codex review-mode runtime.
|
||||
|
||||
Use when:
|
||||
|
||||
- the user asks for Codex review, autoreview, or second-model review
|
||||
- after non-trivial code edits, before final/commit/ship
|
||||
- reviewing a local branch or PR branch after fixes
|
||||
- closing out ClawHub maintainer work that touched source, tests, Convex, UI,
|
||||
CLI packages, or workflows
|
||||
|
||||
## Contract
|
||||
|
||||
- Treat review output as advisory. Never blindly apply it.
|
||||
- Verify every finding by reading the real code path and adjacent files.
|
||||
- Read dependency docs/source/types when the finding depends on external
|
||||
behavior.
|
||||
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes
|
||||
that over-complicate the codebase.
|
||||
- Prefer small fixes at the right ownership boundary; no refactor unless it
|
||||
clearly improves the bug class.
|
||||
- Keep going until the selected review path returns no accepted/actionable
|
||||
findings.
|
||||
- If a review-triggered fix changes code, rerun focused tests and rerun the
|
||||
review helper.
|
||||
- Default to Codex review. If Codex is unavailable or exits with an error, the
|
||||
helper can fall back to `claude -p`, `pi -p`, `opencode run`, `droid exec`, or
|
||||
`copilot`.
|
||||
- Stop as soon as the review command/helper exits 0 with no
|
||||
accepted/actionable findings. Do not run an extra direct `codex review` just
|
||||
to get a nicer clean line, a second opinion, or clearer closeout wording.
|
||||
- If rejecting a finding as intentional/not worth fixing, add a brief inline
|
||||
code comment only when it explains a real invariant or ownership decision
|
||||
future reviewers should know.
|
||||
- Do not push just to review. Push only when the user requested push/ship/PR
|
||||
update.
|
||||
|
||||
## ClawHub Proof Routing
|
||||
|
||||
Pick the smallest proof that matches the touched surface:
|
||||
|
||||
| Touched surface | Usual proof |
|
||||
| --- | --- |
|
||||
| Formatting/lint/static repo health | `bun run ci:static` |
|
||||
| Unit-tested source behavior | focused `bunx vitest run ...`, then `bun run ci:unit` when PR-ready |
|
||||
| Convex code | read `convex/_generated/ai/guidelines.md` first; run focused tests and the deploy/typecheck path that covers the change |
|
||||
| Packages/CLI/mod tool | `bun run ci:packages` or the package-specific `verify` script |
|
||||
| Runtime/build/package surface | `bun run ci:types-build`, `bun run ci:e2e-http`, or the matching broader gate |
|
||||
| UI behavior | use `clawhub-ui-proof` with `proof:ui`; publish proof before final PR comments when needed |
|
||||
| Linux/CI-parity validation | use `crabbox`, normally through the repo scripts |
|
||||
|
||||
For Convex query or schema work, apply the repo's Convex rules: prefer indexes
|
||||
over `.filter()` scans, use cursor-based backfills for data shape changes, and
|
||||
verify with the repo's Convex/typecheck path before claiming deploy safety.
|
||||
|
||||
## Pick Target
|
||||
|
||||
Dirty local work:
|
||||
|
||||
```bash
|
||||
codex review --uncommitted
|
||||
```
|
||||
|
||||
Use this only when the patch is actually unstaged/staged/untracked in the
|
||||
current checkout. For committed, pushed, or PR work, point Codex at the commit
|
||||
or branch diff instead. A clean `--uncommitted` review only proves there is no
|
||||
local patch.
|
||||
|
||||
Branch/PR work:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
codex review --base origin/main
|
||||
```
|
||||
|
||||
If an open PR exists, use its actual base:
|
||||
|
||||
```bash
|
||||
base=$(gh pr view --json baseRefName --jq .baseRefName)
|
||||
codex review --base "origin/$base"
|
||||
```
|
||||
|
||||
Do not pass a prompt with `--base`. Some Codex CLI versions reject
|
||||
`codex review --base <ref> -` with `--base <BRANCH> cannot be used with
|
||||
[PROMPT]`. If that happens, rerun plain `codex review --base <ref>` and report
|
||||
that prompt injection was skipped.
|
||||
|
||||
Committed single change:
|
||||
|
||||
```bash
|
||||
codex review --commit HEAD
|
||||
```
|
||||
|
||||
or with the helper:
|
||||
|
||||
```bash
|
||||
.agents/skills/autoreview/scripts/autoreview --mode commit --commit HEAD
|
||||
```
|
||||
|
||||
Use commit review for already-landed or already-pushed work on `main`.
|
||||
Reviewing clean `main` against `origin/main` is usually an empty diff after
|
||||
push. For a small stack, review each commit explicitly or review the branch
|
||||
before merging with `--base`.
|
||||
|
||||
## Parallel Closeout
|
||||
|
||||
Format first if formatting can change line locations. Then it is OK to run
|
||||
tests and review in parallel:
|
||||
|
||||
```bash
|
||||
.agents/skills/autoreview/scripts/autoreview --parallel-tests "bun run ci:static"
|
||||
```
|
||||
|
||||
Tradeoff: tests may force code changes that stale the review. If tests or
|
||||
review lead to code edits, rerun the affected tests and rerun review until no
|
||||
accepted/actionable findings remain. Once that rerun exits cleanly, stop; do
|
||||
not spend another long review cycle on redundant confirmation.
|
||||
|
||||
## Context Efficiency
|
||||
|
||||
Codex review is usually noisy. Default to a subagent filter when subagents are
|
||||
available. Ask it to run the review and return only:
|
||||
|
||||
- actionable findings it accepts
|
||||
- findings it rejects, with one-line reason
|
||||
- exact files/tests to rerun
|
||||
|
||||
Run inline only for tiny changes or when subagents are unavailable.
|
||||
|
||||
## Helper
|
||||
|
||||
Bundled helper:
|
||||
|
||||
```bash
|
||||
.agents/skills/autoreview/scripts/autoreview --help
|
||||
```
|
||||
|
||||
The helper:
|
||||
|
||||
- chooses dirty `--uncommitted` first
|
||||
- otherwise uses current PR base if `gh pr view` works
|
||||
- otherwise uses `origin/main` for non-main branches
|
||||
- auto-runs `bun run ci:static` in parallel when the repo has `package.json`,
|
||||
`bun.lock`, `node_modules`, and a `ci:static` script; disable with
|
||||
`AUTOREVIEW_AUTO_TESTS=0`
|
||||
- use `--mode commit --commit <ref>` for already-committed work, especially
|
||||
clean `main` after landing
|
||||
- should be left in `--mode auto` or forced to `--mode branch` for PR/branch
|
||||
work; do not force `--mode local` after committing
|
||||
- supports `--reviewer codex|claude|pi|opencode|droid|copilot|auto`; `auto`
|
||||
means Codex first
|
||||
- supports `--fallback-reviewer auto|claude|pi|opencode|droid|copilot|none`
|
||||
- falls back only when Codex is unavailable or exits nonzero without findings,
|
||||
not when Codex reports findings
|
||||
- writes only to stdout unless `--output` or `AUTOREVIEW_OUTPUT` is set
|
||||
- supports `--dry-run`, `--parallel-tests`, and commit refs
|
||||
- runs nested review with `--dangerously-bypass-approvals-and-sandbox --sandbox
|
||||
danger-full-access` by default; use `--no-yolo` or `AUTOREVIEW_YOLO=0` to opt
|
||||
out
|
||||
- prints `autoreview clean: no accepted/actionable findings reported` when the
|
||||
selected review command exits 0 and no accepted/actionable findings are
|
||||
reported
|
||||
|
||||
## Final Report
|
||||
|
||||
Include:
|
||||
|
||||
- review command used
|
||||
- tests/proof run
|
||||
- findings accepted/rejected, briefly why
|
||||
- the clean review result from the final helper/review run, or why a remaining
|
||||
finding was consciously rejected
|
||||
|
||||
Do not run another Codex review solely to improve final wording. If the final
|
||||
helper run exited 0 and produced no accepted/actionable findings, report that
|
||||
exact run as clean.
|
||||
|
||||
## PR / CI Closeout
|
||||
|
||||
- Prefer direct run/job APIs after CI starts: `gh run view <run-id> --json jobs`;
|
||||
use PR rollup only for final mergeability.
|
||||
- After rebase, compare `origin/main..HEAD`; drop CI-fix commits already
|
||||
upstream before pushing.
|
||||
- Update the PR body once near the final head unless proof labels are missing
|
||||
or stale enough to block CI.
|
||||
Executable
+543
@@ -0,0 +1,543 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: autoreview [options]
|
||||
|
||||
Options:
|
||||
--mode auto|local|branch|commit
|
||||
Target selection. Default: auto.
|
||||
--base REF
|
||||
Base ref for branch review. Default: PR base or origin/main.
|
||||
--commit REF
|
||||
Commit ref for commit review. Default: HEAD.
|
||||
--reviewer codex|claude|pi|opencode|droid|copilot|auto
|
||||
Review engine. Default: Codex with configured fallback on error.
|
||||
--fallback-reviewer auto|claude|pi|opencode|droid|copilot|none
|
||||
Fallback when Codex is unavailable or exits nonzero without findings.
|
||||
--codex-bin PATH
|
||||
Codex binary. Default: codex.
|
||||
--claude-bin PATH
|
||||
Claude binary. Default: claude.
|
||||
--pi-bin PATH
|
||||
Pi binary. Default: pi.
|
||||
--opencode-bin PATH
|
||||
OpenCode binary. Default: opencode.
|
||||
--droid-bin PATH
|
||||
Droid binary. Default: droid.
|
||||
--copilot-bin PATH
|
||||
GitHub Copilot binary. Default: copilot.
|
||||
--full-access
|
||||
Keep yolo/full-access mode enabled. Default.
|
||||
--no-yolo
|
||||
Run nested Codex review with normal sandbox/approval prompts.
|
||||
--output FILE
|
||||
Also save output to file.
|
||||
--parallel-tests CMD
|
||||
Run review and test command concurrently. Pass "" to disable auto-tests.
|
||||
Default: bun run ci:static when package.json, bun.lock, node_modules, and
|
||||
a ci:static script are present.
|
||||
--dry-run
|
||||
Print selected commands, do not run.
|
||||
-h, --help
|
||||
Show help.
|
||||
|
||||
Modes:
|
||||
local codex review --uncommitted
|
||||
branch codex review --base <ref>
|
||||
commit codex review --commit <ref>
|
||||
auto dirty tree -> local, else PR/current branch -> branch
|
||||
EOF
|
||||
}
|
||||
|
||||
mode=auto
|
||||
base_ref=
|
||||
commit_ref=HEAD
|
||||
reviewer=${AUTOREVIEW_REVIEWER:-${CODEX_REVIEW_REVIEWER:-auto}}
|
||||
fallback_reviewer=${AUTOREVIEW_FALLBACK_REVIEWER:-${CODEX_REVIEW_FALLBACK_REVIEWER:-auto}}
|
||||
codex_bin=${CODEX_BIN:-codex}
|
||||
claude_bin=${CLAUDE_BIN:-claude}
|
||||
pi_bin=${PI_BIN:-pi}
|
||||
opencode_bin=${OPENCODE_BIN:-opencode}
|
||||
droid_bin=${DROID_BIN:-droid}
|
||||
copilot_bin=${COPILOT_BIN:-copilot}
|
||||
yolo=${AUTOREVIEW_YOLO:-${CODEX_REVIEW_YOLO:-1}}
|
||||
output=${AUTOREVIEW_OUTPUT:-${CODEX_REVIEW_OUTPUT:-}}
|
||||
parallel_tests=
|
||||
parallel_tests_set=false
|
||||
parallel_tests_auto=false
|
||||
dry_run=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--mode) mode=${2:-}; shift 2 ;;
|
||||
--base) base_ref=${2:-}; shift 2 ;;
|
||||
--commit) commit_ref=${2:-}; shift 2 ;;
|
||||
--reviewer) reviewer=${2:-}; shift 2 ;;
|
||||
--fallback-reviewer) fallback_reviewer=${2:-}; shift 2 ;;
|
||||
--codex-bin) codex_bin=${2:-}; shift 2 ;;
|
||||
--claude-bin) claude_bin=${2:-}; shift 2 ;;
|
||||
--pi-bin) pi_bin=${2:-}; shift 2 ;;
|
||||
--opencode-bin) opencode_bin=${2:-}; shift 2 ;;
|
||||
--droid-bin) droid_bin=${2:-}; shift 2 ;;
|
||||
--copilot-bin) copilot_bin=${2:-}; shift 2 ;;
|
||||
--full-access) yolo=1; shift ;;
|
||||
--no-yolo) yolo=0; shift ;;
|
||||
--output) output=${2:-}; shift 2 ;;
|
||||
--parallel-tests) parallel_tests=${2:-}; parallel_tests_set=true; shift 2 ;;
|
||||
--dry-run) dry_run=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) usage >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$mode" in
|
||||
auto|local|branch|commit) ;;
|
||||
*) echo "invalid --mode: $mode" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
case "$reviewer" in
|
||||
auto|codex|claude|pi|opencode|droid|copilot) ;;
|
||||
*) echo "invalid --reviewer: $reviewer" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
case "$fallback_reviewer" in
|
||||
auto|claude|pi|opencode|droid|copilot|none) ;;
|
||||
*) echo "invalid --fallback-reviewer: $fallback_reviewer" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
repo_root=$(git rev-parse --show-toplevel)
|
||||
current_branch=$(git branch --show-current 2>/dev/null || true)
|
||||
dirty=false
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
dirty=true
|
||||
fi
|
||||
|
||||
codex_args=()
|
||||
case "$yolo" in
|
||||
0|false|False|FALSE|no|No|NO|off|Off|OFF) ;;
|
||||
*) codex_args+=(--dangerously-bypass-approvals-and-sandbox --sandbox danger-full-access) ;;
|
||||
esac
|
||||
|
||||
has_package_script() {
|
||||
local script_name=$1
|
||||
command -v node >/dev/null 2>&1 || return 1
|
||||
node -e '
|
||||
const { readFileSync } = require("node:fs");
|
||||
const pkg = JSON.parse(readFileSync(process.argv[1], "utf8"));
|
||||
process.exit(pkg.scripts?.[process.argv[2]] ? 0 : 1);
|
||||
' "$repo_root/package.json" "$script_name" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
auto_tests_disabled() {
|
||||
case "${AUTOREVIEW_AUTO_TESTS:-${CODEX_REVIEW_AUTO_TESTS:-1}}" in
|
||||
0|false|False|FALSE|no|No|NO|off|Off|OFF) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pr_url=
|
||||
if [[ -z "$base_ref" && "$mode" != local ]] && command -v gh >/dev/null 2>&1; then
|
||||
if pr_lines=$(gh pr view --json baseRefName,url --jq '[.baseRefName, .url] | @tsv' 2>/dev/null); then
|
||||
base_name=${pr_lines%%$'\t'*}
|
||||
pr_url=${pr_lines#*$'\t'}
|
||||
if [[ -n "$base_name" ]]; then
|
||||
base_ref="origin/$base_name"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$base_ref" ]]; then
|
||||
base_ref=origin/main
|
||||
fi
|
||||
|
||||
review_kind=
|
||||
if [[ "$mode" == local || ( "$mode" == auto && "$dirty" == true ) ]]; then
|
||||
review_kind=local
|
||||
elif [[ "$mode" == commit ]]; then
|
||||
review_kind=commit
|
||||
elif [[ "$mode" == branch || ( "$mode" == auto && -n "$current_branch" && "$current_branch" != "main" ) ]]; then
|
||||
review_kind=branch
|
||||
else
|
||||
echo "no review target: clean main checkout and no forced mode" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$review_kind" == local ]]; then
|
||||
review_cmd=("$codex_bin" "${codex_args[@]}" review --uncommitted)
|
||||
elif [[ "$review_kind" == commit ]]; then
|
||||
review_cmd=("$codex_bin" "${codex_args[@]}" review --commit "$commit_ref")
|
||||
else
|
||||
review_cmd=("$codex_bin" "${codex_args[@]}" review --base "$base_ref")
|
||||
fi
|
||||
|
||||
if [[ "$parallel_tests_set" == false && -z "$parallel_tests" ]] && ! auto_tests_disabled; then
|
||||
if [[ -f "$repo_root/package.json" && -f "$repo_root/bun.lock" && -d "$repo_root/node_modules" ]] &&
|
||||
command -v bun >/dev/null 2>&1 && has_package_script ci:static; then
|
||||
printf -v quoted_repo_root '%q' "$repo_root"
|
||||
parallel_tests="cd $quoted_repo_root && bun run ci:static"
|
||||
parallel_tests_auto=true
|
||||
fi
|
||||
fi
|
||||
|
||||
printf 'autoreview target: %s\n' "$review_kind"
|
||||
printf 'branch: %s\n' "${current_branch:-detached}"
|
||||
if [[ -n "$pr_url" ]]; then
|
||||
printf 'pr: %s\n' "$pr_url"
|
||||
fi
|
||||
if [[ "$reviewer" == auto ]]; then
|
||||
printf 'reviewer: codex\n'
|
||||
else
|
||||
printf 'reviewer: %s\n' "$reviewer"
|
||||
fi
|
||||
if [[ "$reviewer" == auto || "$reviewer" == codex ]]; then
|
||||
printf 'review:'
|
||||
printf ' %q' "${review_cmd[@]}"
|
||||
printf '\n'
|
||||
else
|
||||
printf 'review: %s prompt review\n' "$reviewer"
|
||||
fi
|
||||
if [[ -n "$parallel_tests" ]]; then
|
||||
printf 'tests: %s' "$parallel_tests"
|
||||
if [[ "$parallel_tests_auto" == true ]]; then
|
||||
printf ' (auto)'
|
||||
fi
|
||||
printf '\n'
|
||||
fi
|
||||
if [[ "$review_kind" == branch ]]; then
|
||||
printf 'fetch: git fetch origin --quiet\n'
|
||||
fi
|
||||
if [[ -n "$output" ]]; then
|
||||
printf 'output: %s\n' "$output"
|
||||
fi
|
||||
if [[ "$dry_run" == true ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$review_kind" == branch ]]; then
|
||||
git fetch origin --quiet || {
|
||||
echo "warning: git fetch origin failed; reviewing with existing refs" >&2
|
||||
}
|
||||
fi
|
||||
|
||||
review_output=$output
|
||||
review_output_is_temp=false
|
||||
prompt_file=
|
||||
if [[ -z "$review_output" ]]; then
|
||||
review_output=$(mktemp)
|
||||
review_output_is_temp=true
|
||||
fi
|
||||
mkdir -p "$(dirname "$review_output")"
|
||||
: > "$review_output"
|
||||
|
||||
cleanup() {
|
||||
if [[ "${review_output_is_temp:-false}" == true && -n "${review_output:-}" ]]; then
|
||||
rm -f "$review_output"
|
||||
fi
|
||||
if [[ -n "${prompt_file:-}" ]]; then
|
||||
rm -f "$prompt_file"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
diff_for_review() {
|
||||
case "$review_kind" in
|
||||
local)
|
||||
git -C "$repo_root" diff --stat
|
||||
git -C "$repo_root" diff --cached --stat
|
||||
git -C "$repo_root" diff --find-renames
|
||||
git -C "$repo_root" diff --cached --find-renames
|
||||
while IFS= read -r untracked_file; do
|
||||
[[ -n "$untracked_file" ]] || continue
|
||||
git -C "$repo_root" diff --no-index -- /dev/null "$untracked_file" || true
|
||||
done < <(git -C "$repo_root" ls-files --others --exclude-standard)
|
||||
;;
|
||||
commit)
|
||||
git -C "$repo_root" show --find-renames --stat --format=fuller "$commit_ref"
|
||||
git -C "$repo_root" show --find-renames --format=medium "$commit_ref"
|
||||
;;
|
||||
branch)
|
||||
git -C "$repo_root" diff --find-renames --stat "$base_ref"...HEAD
|
||||
git -C "$repo_root" diff --find-renames "$base_ref"...HEAD
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
build_prompt_file() {
|
||||
prompt_file=$(mktemp)
|
||||
{
|
||||
cat <<'EOF'
|
||||
You are reviewing a ClawHub diff.
|
||||
|
||||
Return only accepted/actionable findings. Verify claims against the diff and
|
||||
reject speculative, low-value, or overbroad rewrites.
|
||||
|
||||
Use this format for findings:
|
||||
[P1] Short title
|
||||
File: path:line
|
||||
Why: one sentence
|
||||
Fix: one sentence
|
||||
|
||||
If no accepted/actionable findings, output exactly:
|
||||
autoreview clean: no accepted/actionable findings reported
|
||||
|
||||
Diff:
|
||||
EOF
|
||||
diff_for_review
|
||||
} > "$prompt_file"
|
||||
}
|
||||
|
||||
review_output_has_clean_marker() {
|
||||
local path=$1
|
||||
grep -Eq '^[^[:alnum:]]*autoreview clean: no accepted/actionable findings reported[[:space:]]*$' "$path"
|
||||
}
|
||||
|
||||
review_output_has_findings() {
|
||||
grep -Eq '\[P[0-3]\]' "$review_output"
|
||||
}
|
||||
|
||||
review_output_empty() {
|
||||
[[ ! -s "$review_output" ]] || ! grep -q '[^[:space:]]' "$review_output"
|
||||
}
|
||||
|
||||
review_output_used_prompt_reviewer() {
|
||||
grep -Eq '^fallback: (claude -p|pi -p|opencode run|droid exec|copilot)$' "$review_output"
|
||||
}
|
||||
|
||||
run_codex_review() {
|
||||
if ! command -v "$codex_bin" >/dev/null 2>&1; then
|
||||
echo "codex reviewer unavailable: $codex_bin" >&2
|
||||
return 127
|
||||
fi
|
||||
"${review_cmd[@]}" 2>&1 | tee "$review_output"
|
||||
}
|
||||
|
||||
run_prompt_reviewer() {
|
||||
local selected=$1
|
||||
local status=0
|
||||
local prompt_bytes=0
|
||||
local copilot_prompt=
|
||||
build_prompt_file
|
||||
case "$selected" in
|
||||
claude)
|
||||
command -v "$claude_bin" >/dev/null 2>&1 || {
|
||||
echo "fallback reviewer unavailable: $claude_bin" >&2
|
||||
return 127
|
||||
}
|
||||
printf 'fallback: claude -p\n' | tee -a "$review_output"
|
||||
"$claude_bin" --tools "" --no-session-persistence -p < "$prompt_file" 2>&1 | tee -a "$review_output"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
pi)
|
||||
command -v "$pi_bin" >/dev/null 2>&1 || {
|
||||
echo "fallback reviewer unavailable: $pi_bin" >&2
|
||||
return 127
|
||||
}
|
||||
printf 'fallback: pi -p\n' | tee -a "$review_output"
|
||||
"$pi_bin" --no-tools --no-session -p < "$prompt_file" 2>&1 | tee -a "$review_output"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
opencode)
|
||||
command -v "$opencode_bin" >/dev/null 2>&1 || {
|
||||
echo "fallback reviewer unavailable: $opencode_bin" >&2
|
||||
return 127
|
||||
}
|
||||
printf 'fallback: opencode run\n' | tee -a "$review_output"
|
||||
"$opencode_bin" run --pure --dir "$repo_root" "Review the attached prompt file. Do not modify files." --file "$prompt_file" 2>&1 | tee -a "$review_output"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
droid)
|
||||
command -v "$droid_bin" >/dev/null 2>&1 || {
|
||||
echo "fallback reviewer unavailable: $droid_bin" >&2
|
||||
return 127
|
||||
}
|
||||
printf 'fallback: droid exec\n' | tee -a "$review_output"
|
||||
"$droid_bin" exec --cwd "$repo_root" -f "$prompt_file" 2>&1 | tee -a "$review_output"
|
||||
status=${PIPESTATUS[0]}
|
||||
;;
|
||||
copilot)
|
||||
command -v "$copilot_bin" >/dev/null 2>&1 || {
|
||||
echo "fallback reviewer unavailable: $copilot_bin" >&2
|
||||
return 127
|
||||
}
|
||||
printf 'fallback: copilot\n' | tee -a "$review_output"
|
||||
prompt_bytes=$(wc -c < "$prompt_file" | tr -d '[:space:]')
|
||||
if (( prompt_bytes > 120000 )); then
|
||||
echo "copilot reviewer unavailable: generated prompt is too large" | tee -a "$review_output"
|
||||
status=1
|
||||
else
|
||||
copilot_prompt=$(< "$prompt_file")
|
||||
"$copilot_bin" -C "$repo_root" --available-tools=none --stream off --output-format text --silent -p "$copilot_prompt" 2>&1 | tee -a "$review_output"
|
||||
status=${PIPESTATUS[0]}
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "unsupported prompt reviewer: $selected" >&2
|
||||
status=2
|
||||
;;
|
||||
esac
|
||||
rm -f "$prompt_file"
|
||||
prompt_file=
|
||||
return "$status"
|
||||
}
|
||||
|
||||
fallback_reviewer_is_available() {
|
||||
local selected=$1
|
||||
case "$selected" in
|
||||
claude) command -v "$claude_bin" >/dev/null 2>&1 ;;
|
||||
pi) command -v "$pi_bin" >/dev/null 2>&1 ;;
|
||||
opencode) command -v "$opencode_bin" >/dev/null 2>&1 ;;
|
||||
droid) command -v "$droid_bin" >/dev/null 2>&1 ;;
|
||||
copilot) command -v "$copilot_bin" >/dev/null 2>&1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_selected_review() {
|
||||
local selected=$1
|
||||
case "$selected" in
|
||||
codex) run_codex_review ;;
|
||||
claude|pi|opencode|droid|copilot) run_prompt_reviewer "$selected" ;;
|
||||
*) echo "unsupported reviewer: $selected" >&2; return 2 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_auto_fallback_review() {
|
||||
local selected
|
||||
if [[ "$fallback_reviewer" != auto ]]; then
|
||||
run_selected_review "$fallback_reviewer"
|
||||
return $?
|
||||
fi
|
||||
for selected in claude pi opencode droid copilot; do
|
||||
if fallback_reviewer_is_available "$selected"; then
|
||||
run_selected_review "$selected"
|
||||
return $?
|
||||
fi
|
||||
done
|
||||
echo "fallback reviewer unavailable: no configured fallback CLI found" >&2
|
||||
return 127
|
||||
}
|
||||
|
||||
run_auto_review() {
|
||||
local status=0
|
||||
run_selected_review codex
|
||||
status=$?
|
||||
if [[ "$status" == 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
if (( status > 128 && status < 192 )); then
|
||||
return "$status"
|
||||
fi
|
||||
if review_output_has_findings; then
|
||||
return "$status"
|
||||
fi
|
||||
if [[ "$fallback_reviewer" == none ]]; then
|
||||
return "$status"
|
||||
fi
|
||||
if [[ "$fallback_reviewer" == auto ]]; then
|
||||
printf 'autoreview warning: codex exited %s; trying configured fallback reviewers\n' "$status" >&2
|
||||
else
|
||||
printf 'autoreview warning: codex exited %s; falling back to %s\n' "$status" "$fallback_reviewer" >&2
|
||||
fi
|
||||
run_auto_fallback_review
|
||||
}
|
||||
|
||||
elapsed_since() {
|
||||
local started_at=$1
|
||||
local finished_at
|
||||
finished_at=$(date +%s)
|
||||
printf '%s\n' "$((finished_at - started_at))"
|
||||
}
|
||||
|
||||
format_elapsed() {
|
||||
local seconds=$1
|
||||
if (( seconds < 60 )); then
|
||||
printf '%ss\n' "$seconds"
|
||||
else
|
||||
printf '%sm%ss\n' "$((seconds / 60))" "$((seconds % 60))"
|
||||
fi
|
||||
}
|
||||
|
||||
report_clean_review_or_fail() {
|
||||
local elapsed_text
|
||||
elapsed_text=$(format_elapsed "${review_elapsed_seconds:-0}")
|
||||
if review_output_has_findings; then
|
||||
printf 'autoreview complete after %s\n' "$elapsed_text"
|
||||
printf 'autoreview findings: accepted/actionable findings reported\n'
|
||||
return 1
|
||||
fi
|
||||
if review_output_empty; then
|
||||
printf 'autoreview complete after %s; no output\n' "$elapsed_text"
|
||||
return 1
|
||||
fi
|
||||
if review_output_used_prompt_reviewer && ! review_output_has_clean_marker "$review_output"; then
|
||||
printf 'autoreview complete after %s\n' "$elapsed_text"
|
||||
printf 'autoreview findings: prompt reviewer did not emit clean marker\n'
|
||||
return 1
|
||||
fi
|
||||
printf 'autoreview complete after %s\n' "$elapsed_text"
|
||||
printf 'autoreview clean: no accepted/actionable findings reported\n'
|
||||
}
|
||||
|
||||
if [[ -z "$parallel_tests" ]]; then
|
||||
review_started_at=$(date +%s)
|
||||
set +e
|
||||
if [[ "$reviewer" == auto ]]; then
|
||||
run_auto_review
|
||||
else
|
||||
run_selected_review "$reviewer"
|
||||
fi
|
||||
review_status=$?
|
||||
review_elapsed_seconds=$(elapsed_since "$review_started_at")
|
||||
set -e
|
||||
if [[ "$review_status" == 0 ]]; then
|
||||
report_clean_review_or_fail
|
||||
exit $?
|
||||
fi
|
||||
exit "$review_status"
|
||||
fi
|
||||
|
||||
review_status_file=$(mktemp)
|
||||
review_elapsed_file=$(mktemp)
|
||||
tests_status_file=$(mktemp)
|
||||
|
||||
(
|
||||
set +e
|
||||
review_started_at=$(date +%s)
|
||||
if [[ "$reviewer" == auto ]]; then
|
||||
run_auto_review
|
||||
else
|
||||
run_selected_review "$reviewer"
|
||||
fi
|
||||
status=$?
|
||||
elapsed=$(elapsed_since "$review_started_at")
|
||||
printf '%s\n' "$status" > "$review_status_file"
|
||||
printf '%s\n' "$elapsed" > "$review_elapsed_file"
|
||||
) &
|
||||
review_pid=$!
|
||||
|
||||
(
|
||||
set +e
|
||||
bash -lc "$parallel_tests"
|
||||
status=$?
|
||||
printf '%s\n' "$status" > "$tests_status_file"
|
||||
) &
|
||||
tests_pid=$!
|
||||
|
||||
wait "$review_pid" || true
|
||||
wait "$tests_pid" || true
|
||||
|
||||
review_status=$(cat "$review_status_file")
|
||||
review_elapsed_seconds=$(cat "$review_elapsed_file")
|
||||
tests_status=$(cat "$tests_status_file")
|
||||
rm -f "$review_status_file" "$review_elapsed_file" "$tests_status_file"
|
||||
|
||||
printf 'autoreview exit: %s\n' "$review_status"
|
||||
printf 'tests exit: %s\n' "$tests_status"
|
||||
|
||||
if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
report_clean_review_or_fail
|
||||
@@ -49,6 +49,8 @@ skills-lock.json
|
||||
!.agents/skills/clawhub-pr-maintainer/**
|
||||
!.agents/skills/clawhub-moderation/
|
||||
!.agents/skills/clawhub-moderation/**
|
||||
!.agents/skills/autoreview/
|
||||
!.agents/skills/autoreview/**
|
||||
skills/*
|
||||
.codex/*
|
||||
!.codex/environments/
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
- Commit messages: Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`…).
|
||||
- Keep changes scoped; avoid repo-wide search/replace.
|
||||
- Before commit/PR handoff, run `bun run ci:static` so formatting, linting, audit/peer checks, and dead-code export checks match the CI `static` job. For faster inner loops, targeted `bun run format:check -- <files>` / `bun run lint` are fine, but do not treat them as the final pre-push gate.
|
||||
- Before commit/PR handoff for non-trivial code changes, use `$autoreview` until no accepted/actionable findings remain, unless equivalent manual review already happened, the change is trivial/docs-only, or the user opts out.
|
||||
- Before opening a PR for source or test changes, run the targeted tests for the touched behavior and `bun run ci:unit` (`VITE_CONVEX_URL=https://example.invalid bun run coverage`) unless the change is docs/config-only or the user explicitly asks to rely on CI. For runtime, build, or package changes, also run the matching broader gate when it covers the touched surface: `bun run ci:types-build`, `bun run ci:packages`, `bun run ci:e2e-http`, or `bun run ci:playwright-smoke`.
|
||||
- PRs: include summary + test commands run. Add screenshots for UI changes.
|
||||
- Before merging any PR, verify TypeScript cleanly with `bunx tsc -p packages/schema/tsconfig.json --noEmit` and `bunx tsc -p packages/clawhub/tsconfig.json --noEmit`; if Convex code changed, also run the repo typecheck path used by deploy so `bunx convex deploy` will not fail on `tsc`.
|
||||
|
||||
Reference in New Issue
Block a user