From 8a46fdaa78a9ad4f47864ac9e637363f30dedbef Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Sun, 22 Feb 2026 00:07:36 +0200 Subject: [PATCH] ci: add shared CI workflows, Claude Code review, and git hooks - CI workflow calling reusable workflow from agent-sh/.github - Claude Code automated PR review (owner/member/collaborator only, max 3 runs) - Claude Code @mentions support - Pre-push hook running tests before push --- .github/workflows/ci.yml | 12 ++++ .github/workflows/claude-code-review.yml | 77 ++++++++++++++++++++++++ .github/workflows/claude.yml | 57 ++++++++++++++++++ scripts/pre-push-node | 9 +++ scripts/setup-hooks.sh | 50 +++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml create mode 100755 scripts/pre-push-node create mode 100755 scripts/setup-hooks.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..46580a0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,12 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + ci: + uses: agent-sh/.github/.github/workflows/ci-node.yml@main + secrets: inherit diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..5655b4b --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,77 @@ +name: Claude Code - Automated PR Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + +jobs: + claude-review: + if: >- + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), + github.event.pull_request.author_association) + + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + issues: write + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Check run count + id: check-runs + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + EVENT_ACTION: ${{ github.event.action }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + WORKFLOW_FILE="claude-code-review.yml" + + RUN_COUNT=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "repos/${REPO}/actions/workflows/${WORKFLOW_FILE}/runs?head_sha=${HEAD_SHA}&status=completed" \ + --jq '[.workflow_runs[] | select(.event == "pull_request")] | length' || echo "") + + RUN_COUNT=${RUN_COUNT:-0} + if ! echo "$RUN_COUNT" | grep -qE '^[0-9]+$'; then + echo "[WARN] Invalid RUN_COUNT value ('$RUN_COUNT') from GitHub API. Defaulting to 0." + RUN_COUNT=0 + fi + + RUN_COUNT=$((RUN_COUNT + 1)) + + echo "This is run #$RUN_COUNT for this PR (event: $EVENT_ACTION)" + + if [ "$RUN_COUNT" -le 3 ]; then + echo "should_run=true" >> $GITHUB_OUTPUT + echo "[OK] Will run Claude review (run $RUN_COUNT of 3)" + else + echo "should_run=false" >> $GITHUB_OUTPUT + echo "[SKIP] Skipping Claude review (already ran 3 times)" + fi + + - name: Run Claude Code Review + if: steps.check-runs.outputs.should_run == 'true' + uses: anthropics/claude-code-action@f64219702d7454cf29fe32a74104be6ed43dc637 # v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + model: claude-opus-4-6 + use_commit_signing: true + + - name: Comment on PR (skipped) + if: steps.check-runs.outputs.should_run == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + gh pr comment "$PR_NUMBER" --body "[INFO] Claude Code review was skipped as it has already run 3 times for this PR." diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..adb83cd --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,57 @@ +name: Claude Code - @mentions + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + pull_request_review: + types: [submitted] + issues: + types: [opened] + +jobs: + claude-mention: + if: | + ( + github.event_name == 'issue_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + ) || + ( + github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + ) || + ( + github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) + ) || + ( + github.event_name == 'issues' && + (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + ) + + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + issues: write + actions: read + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Run Claude Code + uses: anthropics/claude-code-action@f64219702d7454cf29fe32a74104be6ed43dc637 # v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + model: claude-opus-4-6 + use_commit_signing: true diff --git a/scripts/pre-push-node b/scripts/pre-push-node new file mode 100755 index 0000000..def1a46 --- /dev/null +++ b/scripts/pre-push-node @@ -0,0 +1,9 @@ +#!/bin/sh +# Pre-push hook for Node.js projects +# Runs tests before allowing push + +cd "$(git rev-parse --show-toplevel)" || exit 1 + +echo "[INFO] Running pre-push tests..." +npm test +exit $? diff --git a/scripts/setup-hooks.sh b/scripts/setup-hooks.sh new file mode 100755 index 0000000..aa9a534 --- /dev/null +++ b/scripts/setup-hooks.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# Setup git hooks for agent-sh repos +# Detects project type and installs the appropriate pre-push hook +# +# Usage: sh scripts/setup-hooks.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Verify we are inside a git repository +if ! git -C "$REPO_ROOT" rev-parse --git-dir >/dev/null 2>&1; then + echo "[ERROR] Not a git repository: $REPO_ROOT" + exit 1 +fi + +# Resolve hooks directory (handles worktrees where .git is a file) +if [ -f "$REPO_ROOT/.git" ]; then + COMMON_DIR="$(git -C "$REPO_ROOT" rev-parse --git-common-dir)" + HOOKS_DIR="$COMMON_DIR/hooks" +else + HOOKS_DIR="$REPO_ROOT/.git/hooks" +fi + +mkdir -p "$HOOKS_DIR" + +# Detect project type and install hook +if [ -f "$REPO_ROOT/Cargo.toml" ]; then + echo "[INFO] Detected Rust project" + if [ ! -f "$SCRIPT_DIR/pre-push-rust" ]; then + echo "[ERROR] Missing hook script: $SCRIPT_DIR/pre-push-rust" + exit 1 + fi + cp "$SCRIPT_DIR/pre-push-rust" "$HOOKS_DIR/pre-push" + chmod +x "$HOOKS_DIR/pre-push" + echo "[OK] Installed pre-push hook (Rust)" +elif [ -f "$REPO_ROOT/package.json" ]; then + echo "[INFO] Detected Node.js project" + if [ ! -f "$SCRIPT_DIR/pre-push-node" ]; then + echo "[ERROR] Missing hook script: $SCRIPT_DIR/pre-push-node" + exit 1 + fi + cp "$SCRIPT_DIR/pre-push-node" "$HOOKS_DIR/pre-push" + chmod +x "$HOOKS_DIR/pre-push" + echo "[OK] Installed pre-push hook (Node.js)" +else + echo "[WARN] No package.json or Cargo.toml found - skipping hook install" + exit 0 +fi