diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3db319f --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Required — both tokens must be created manually +CLAUDE_CODE_OAUTH_TOKEN="your-oauth-token" +AGENTIC_BOT_TOKEN="your-bot-pat" + +# Optional — derived automatically from the bot token if omitted +# AGENTIC_BOT_NAME="your-bot-username" +# AGENTIC_BOT_EMAIL="123+bot@users.noreply.github.com" diff --git a/README.md b/README.md index 06ac8f2..938741b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,93 @@ # Agentic -Reusable Claude Code skills and GitHub Actions composite actions for automated PR lifecycle management. +**Ship features by talking to GitHub.** + +Run Claude Code on GitHub, not just your laptop. Work on multiple features at the same time. Every change goes through a pull request, so the whole team can see and review what the AI builds. Comment `@claude` on an issue — it writes the code, opens a PR, fixes review feedback, and merges. Everything tracked in git. + +## Repo Setup + +Run the install script to set up a client repo: + +```bash +bin/install +``` + +This interactively: +1. Checks prerequisites (bot account + PAT, Claude OAuth token) +2. Creates or connects to a repo +3. Configures repo settings (squash merge, delete branch on merge, no auto-merge) +4. Copies 4 thin workflow files from `templates/workflows/` into the client repo's `.github/workflows/` +5. Optionally sets secrets and variables +6. Adds the bot as a collaborator +7. Creates a CLAUDE.md template +8. Commits and pushes + +For non-interactive use, CI, or to resume an interrupted install, pass `--repo` and `--path` to skip the repo step: + +```bash +bin/install --repo acme/my-app --path ./my-app +``` + +Already-configured settings are applied idempotently, and the workflow step will ask before overwriting existing files. This makes it safe to re-run after a failure. + +### Skipping prompts with `.env` + +To avoid re-typing secrets when setting up multiple client repos, create a `.env` file in the agentic repo root: + +```bash +CLAUDE_CODE_OAUTH_TOKEN="your-oauth-token" +AGENTIC_BOT_TOKEN="your-bot-pat" +``` + +The install script loads `.env` automatically if it exists. The bot's username and noreply email are derived from the token automatically — you only need the two tokens. The file is already in `.gitignore`. + +### Workflow templates + +The system has two layers: **workflow files** installed into each client repo, and **composite actions** that live in the agentic repo. + +The workflow files are thin wiring — they define which GitHub events trigger which actions in which order. The templates live in `templates/workflows/` and use a `__AGENTIC_REPO__` placeholder that the install script replaces with this repo's `owner/repo` path: + +| Template | Trigger | What it does | +|----------|---------|-------------| +| `gh-commented.yml` | Issue comment or new issue starting with `@claude` | Responds to questions | +| `gh-start-work.yml` | Issue comment with `@claude /pr-start` | Implements issue, opens PR | +| `pr-changes-requested.yml` | PR review with changes requested starting with `@claude` | Fixes code, pushes | +| `pr-approved.yml` | PR review approved starting with `@claude` | Squash merges | + +The composite actions are where the real logic lives. Because the workflows reference them at `@main`, every client repo pulls the latest version of each action on every run. You update an action once in the agentic repo, and every client gets the change automatically — no need to re-run the install script or touch the workflow files. + +### Required secrets and variables + +**Secrets** (Settings → Secrets and variables → Actions): +- `CLAUDE_CODE_OAUTH_TOKEN`: OAuth token from `claude setup-token` +- `AGENTIC_BOT_TOKEN`: The bot account's fine-grained PAT + +**Variables** (Settings → Secrets and variables → Actions → Variables): +- `AGENTIC_BOT_NAME`: The bot's GitHub username +- `AGENTIC_BOT_EMAIL`: The bot's noreply email + +### Diagnostics + +To verify a client repo is set up correctly: + +```bash +bin/diagnostics owner/repo +``` + +### Custom environment variables + +To pass project-specific env vars (e.g. for a dev server proxy), add an `env` block at the job level in the client workflow. Composite actions inherit job-level env vars automatically: + +```yaml +jobs: + pr-fix: + env: + VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} + VITE_HOST: localhost + steps: + - uses: //.github/actions/run-claude-skill@main + # VITE_PROXY_TARGET and VITE_HOST are automatically available +``` ## Skills @@ -109,42 +196,9 @@ This only happens when the plan involves UI work. The GitHub Actions runner has ## Bot Identity -In GitHub Actions, the workflow sets git identity directly using `AGENTIC_BOT_NAME` and `AGENTIC_BOT_EMAIL` variables, and authenticates with `AGENTIC_BOT_TOKEN`. Claude never runs git or gh commands — the workflow handles all git and GitHub operations. - -For local use, `bin/git-bot` is a wrapper script that sets the bot's identity via environment variables. This ensures commits, PRs, and comments are attributed to your machine account, not your personal GitHub account. - -### Setting up a GitHub machine account - -1. Create a new GitHub account for your bot (e.g. `yourname-bot`) -2. Add the bot account as a collaborator on your repos (with write access) -3. On the bot account, generate a fine-grained Personal Access Token (PAT): - - Settings → Developer settings → Personal access tokens → Fine-grained tokens - - Repository access: **All repositories** (covers any repo the bot is added to in the future) - - Permissions: - - **Contents**: Read and write (for git push) - - **Pull requests**: Read and write (for creating/commenting/merging PRs) - - **Metadata**: Read (auto-selected) -4. Note the bot's noreply email from https://github.com/settings/emails — it looks like `ID+username@users.noreply.github.com` - -### Environment variables - -`git-bot` requires three env vars: - -| Variable | Value | -|---|---| -| `AGENTIC_BOT_NAME` | The bot's GitHub username (e.g. `yourname-bot`) | -| `AGENTIC_BOT_EMAIL` | The bot's noreply email (e.g. `ID+yourname-bot@users.noreply.github.com`) | -| `GH_TOKEN` | The bot's PAT | - -**Locally**, set these in your shell profile or a `.env` file: - -```bash -export AGENTIC_BOT_NAME="yourname-bot" -export AGENTIC_BOT_EMAIL="123456+yourname-bot@users.noreply.github.com" -export GH_TOKEN="ghp_..." -``` +A GitHub machine account (e.g. `yourname-bot`) is used for all git and GitHub operations. Commits, PRs, and comments are attributed to the bot, not your personal account. Claude never runs git or gh commands directly — the workflows handle that. -**In GitHub Actions**, add `AGENTIC_BOT_TOKEN` as a repo/org secret, and `AGENTIC_BOT_NAME` / `AGENTIC_BOT_EMAIL` as repo/org variables (Settings → Secrets and variables → Actions). +`bin/install` walks you through setting up the bot account and its PAT. For local use, `bin/git-bot` is a wrapper that sets the bot's identity via `AGENTIC_BOT_NAME`, `AGENTIC_BOT_EMAIL`, and `GH_TOKEN` environment variables. ## Composite Actions @@ -157,212 +211,6 @@ This repo provides 6 composite actions that can be composed into workflows: - **create-pr** — Creates a PR with optional issue linking from `/tmp/commit_msg.txt` and `/tmp/comment.txt` - **squash-merge** — Squash merges a PR with custom commit message from `/tmp/commit_msg.txt` -### Environment Variable Inheritance - -Composite actions automatically inherit environment variables from the job. Set env vars at the job level and they'll be available to all steps, including those in composite actions: - -```yaml -jobs: - pr-fix: - env: - VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} - VITE_HOST: localhost - steps: - - uses: chriswickett/agentic/.github/actions/run-claude-skill@main - # VITE_PROXY_TARGET and VITE_HOST are automatically available -``` - -Secrets and tokens must still be passed explicitly as inputs. - -## Repo Setup - -Before using these workflows with a client repo: - -1. **Disable auto-merge**: Settings → General → Pull Requests. Make sure "Allow auto-merge" is OFF. GClaude controls when merges happen. - -2. **Allow squash merging**: Same section, make sure "Allow squash merging" is ON. - -3. **Add workflow files**: Create four workflow files in `.github/workflows/`. All workflows require `@claude` at the start of the triggering comment/review for security. - - `gh-commented.yml` — Responds to issue comments and new issues: - ```yaml - on: - issue_comment: - types: [created] - issues: - types: [opened] - - jobs: - gh-respond: - if: | - (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '@claude') && !startsWith(github.event.comment.body, '@claude /pr-start')) || - (github.event_name == 'issues' && startsWith(github.event.issue.body, '@claude')) - runs-on: ubuntu-latest - timeout-minutes: 5 - env: - VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} - VITE_HOST: localhost - steps: - - uses: actions/checkout@v4 - - - uses: chriswickett/agentic/.github/actions/setup-claude-agent@main - - - uses: chriswickett/agentic/.github/actions/run-claude-skill@main - with: - skill-name: gh-respond - context-type: issue - context-number: ${{ github.event.issue.number }} - repository: ${{ github.repository }} - claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - - - uses: chriswickett/agentic/.github/actions/post-comment@main - with: - comment-file: /tmp/comment.txt - issue-or-pr-number: ${{ github.event.issue.number }} - comment-type: issue - repository: ${{ github.repository }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - ``` - - `gh-start-work.yml` — Implements an issue and opens a PR: - ```yaml - on: - issue_comment: - types: [created] - - jobs: - pr-start: - if: "!github.event.issue.pull_request && startsWith(github.event.comment.body, '@claude /pr-start')" - runs-on: ubuntu-latest - timeout-minutes: 15 - env: - VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} - VITE_HOST: localhost - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: chriswickett/agentic/.github/actions/setup-claude-agent@main - - - uses: chriswickett/agentic/.github/actions/run-claude-skill@main - with: - skill-name: pr-start - context-type: issue - context-number: ${{ github.event.issue.number }} - repository: ${{ github.repository }} - claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - - - uses: chriswickett/agentic/.github/actions/git-commit-push@main - with: - branch-name-file: /tmp/pr_branch_name.txt - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - bot-name: ${{ vars.AGENTIC_BOT_NAME }} - bot-email: ${{ vars.AGENTIC_BOT_EMAIL }} - - - uses: chriswickett/agentic/.github/actions/create-pr@main - with: - issue-number: ${{ github.event.issue.number }} - repository: ${{ github.repository }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - ``` - - `pr-changes-requested.yml` — Fixes code when reviewer requests changes: - ```yaml - on: - pull_request_review: - types: [submitted] - - jobs: - pr-fix: - if: github.event.review.state == 'changes_requested' && startsWith(github.event.review.body, '@claude') - runs-on: ubuntu-latest - timeout-minutes: 10 - env: - VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} - VITE_HOST: localhost - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - uses: chriswickett/agentic/.github/actions/setup-claude-agent@main - - - uses: chriswickett/agentic/.github/actions/run-claude-skill@main - with: - skill-name: pr-fix - context-type: pr - context-number: ${{ github.event.pull_request.number }} - repository: ${{ github.repository }} - claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - - - uses: chriswickett/agentic/.github/actions/git-commit-push@main - with: - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - bot-name: ${{ vars.AGENTIC_BOT_NAME }} - bot-email: ${{ vars.AGENTIC_BOT_EMAIL }} - - - uses: chriswickett/agentic/.github/actions/post-comment@main - with: - issue-or-pr-number: ${{ github.event.pull_request.number }} - comment-type: pr - repository: ${{ github.repository }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - ``` - - `pr-approved.yml` — Squash merges when reviewer approves: - ```yaml - on: - pull_request_review: - types: [submitted] - - jobs: - pr-merge: - if: github.event.review.state == 'approved' && startsWith(github.event.review.body, '@claude') - runs-on: ubuntu-latest - timeout-minutes: 5 - env: - VITE_PROXY_TARGET: ${{ secrets.VITE_PROXY_TARGET }} - VITE_HOST: localhost - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - uses: chriswickett/agentic/.github/actions/setup-claude-agent@main - - - uses: chriswickett/agentic/.github/actions/run-claude-skill@main - with: - skill-name: pr-merge - context-type: pr - context-number: ${{ github.event.pull_request.number }} - repository: ${{ github.repository }} - claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - - - uses: chriswickett/agentic/.github/actions/squash-merge@main - with: - pr-number: ${{ github.event.pull_request.number }} - repository: ${{ github.repository }} - github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} - ``` - -4. **Add secrets**: At repo level (Settings → Secrets and variables → Actions) or org level: - - `CLAUDE_CODE_OAUTH_TOKEN`: OAuth token from `claude setup-token` (uses your Claude Pro/Max subscription) - - `AGENTIC_BOT_TOKEN`: The bot account's PAT - - `VITE_PROXY_TARGET` (optional): For frontend repos that need a proxy to a backend - -5. **Add variables**: At repo level (Settings → Secrets and variables → Actions → Variables) or org level: - - `AGENTIC_BOT_NAME`: The bot's GitHub username - - `AGENTIC_BOT_EMAIL`: The bot's noreply email - ## TODO - Harden and expand the allowed commands list in `bin/bg` -- Setup script to generate client repo workflow files from agentic - diff --git a/bin/diagnostics b/bin/diagnostics new file mode 100755 index 0000000..70c2d9c --- /dev/null +++ b/bin/diagnostics @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENTIC_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TEMPLATE_DIR="$AGENTIC_ROOT/templates/workflows" + +source "$SCRIPT_DIR/lib/output.sh" + +[[ -f "$AGENTIC_ROOT/.env" ]] && source "$AGENTIC_ROOT/.env" + +# Override helpers for diagnostic checklist format +ok() { echo -e " ${GREEN}✓${RESET} $*"; } +fail() { echo -e " ${RED}✗${RESET} $*"; } +warn() { echo -e " ${YELLOW}!${RESET} $*"; } + +check_gh_auth() { + echo -e "${BOLD}GitHub CLI${RESET}" + if gh auth status &>/dev/null 2>&1; then + ok "gh authenticated" + else + fail "gh not authenticated — run 'gh auth login'" + fi + echo "" +} + +check_repo_access() { + echo -e "${BOLD}Repo access${RESET}" + if gh api "/repos/$1" &>/dev/null 2>&1; then + ok "Repo $1 is accessible" + else + fail "Cannot access $1 — check the repo exists and you have access" + fi + echo "" +} + +check_repo_settings() { + echo -e "${BOLD}Repo settings${RESET}" + local settings + settings=$(gh api "/repos/$1" --jq '{auto_merge: .allow_auto_merge, squash: .allow_squash_merge, delete_branch: .delete_branch_on_merge}' 2>/dev/null || echo "{}") + + if echo "$settings" | jq -e '.auto_merge == false' &>/dev/null; then + ok "Auto-merge disabled" + else + fail "Auto-merge should be disabled" + fi + if echo "$settings" | jq -e '.squash == true' &>/dev/null; then + ok "Squash merge enabled" + else + fail "Squash merge should be enabled" + fi + if echo "$settings" | jq -e '.delete_branch == true' &>/dev/null; then + ok "Delete branch on merge enabled" + else + fail "Delete branch on merge should be enabled" + fi + echo "" +} + +check_workflow_files() { + echo -e "${BOLD}Workflow files${RESET}" + for template in "$TEMPLATE_DIR"/*.yml; do + local wf=$(basename "$template" .yml) + if gh api "/repos/$1/contents/.github/workflows/${wf}.yml" &>/dev/null 2>&1; then + ok "${wf}.yml" + else + fail "${wf}.yml not found" + fi + done + echo "" +} + +check_secrets() { + echo -e "${BOLD}Secrets${RESET}" + local secrets + secrets=$(gh secret list --repo "$1" 2>/dev/null || echo "") + + if echo "$secrets" | grep -q "CLAUDE_CODE_OAUTH_TOKEN"; then + ok "CLAUDE_CODE_OAUTH_TOKEN is set" + else + fail "CLAUDE_CODE_OAUTH_TOKEN not set" + fi + if echo "$secrets" | grep -q "AGENTIC_BOT_TOKEN"; then + ok "AGENTIC_BOT_TOKEN is set" + else + fail "AGENTIC_BOT_TOKEN not set" + fi + echo "" +} + +check_variables() { + echo -e "${BOLD}Variables${RESET}" + local vars + vars=$(gh variable list --repo "$1" 2>/dev/null || echo "") + + if echo "$vars" | grep -q "AGENTIC_BOT_NAME"; then + BOT_NAME=$(echo "$vars" | grep "AGENTIC_BOT_NAME" | awk '{print $2}') + ok "AGENTIC_BOT_NAME = $BOT_NAME" + else + fail "AGENTIC_BOT_NAME not set" + BOT_NAME="" + fi + if echo "$vars" | grep -q "AGENTIC_BOT_EMAIL"; then + ok "AGENTIC_BOT_EMAIL is set" + else + fail "AGENTIC_BOT_EMAIL not set" + fi + echo "" +} + +check_bot_access() { + echo -e "${BOLD}Bot repo access${RESET}" + local TEST_TOKEN="${AGENTIC_BOT_TOKEN:-}" + if [[ -z "$TEST_TOKEN" ]]; then + echo -n "Paste AGENTIC_BOT_TOKEN to test (or press Enter to skip): " + read -rs TEST_TOKEN + echo "" + fi + + if [[ -n "$TEST_TOKEN" ]]; then + if GH_TOKEN="$TEST_TOKEN" gh api "/repos/$1" &>/dev/null 2>&1; then + ok "Bot token can access $1" + else + fail "Bot token cannot access $1" + cat < Personal access tokens > Pending requests)${RESET} + ${DIM} - Bot is a collaborator or member of a team with repo access${RESET} + ${DIM} - Repository access: All repositories${RESET} + ${DIM} - Permissions: Contents (read/write), Pull requests (read/write), Issues (read/write), Metadata (read)${RESET} +EOF + fi + else + warn "Skipped — paste the bot token to verify access" + fi + echo "" +} + +# --- Main --- + +if [[ -n "${1:-}" ]]; then + REPO="$1" +else + read -rp "Repo to check (owner/repo): " REPO +fi + +echo "" +echo -e "${BOLD}Diagnostics for $REPO${RESET}" +echo "" + +check_gh_auth +check_repo_access "$REPO" +check_repo_settings "$REPO" +check_workflow_files "$REPO" +check_secrets "$REPO" +check_variables "$REPO" +check_bot_access "$REPO" diff --git a/bin/install b/bin/install new file mode 100755 index 0000000..00a54e5 --- /dev/null +++ b/bin/install @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/guards.sh" + +# Parse flags +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) export FULL_REPO="$2"; shift 2 ;; + --path) export CLIENT_DIR="$2"; shift 2 ;; + -y) export NONINTERACTIVE=1; shift ;; + *) err "Unknown flag: $1" ;; + esac +done + +require_commands gh git jq +require_gh_auth + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENTIC_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +[[ -f "$AGENTIC_ROOT/.env" ]] && source "$AGENTIC_ROOT/.env" +AGENTIC_REPO=$(detect_remote "$AGENTIC_ROOT") \ + || err "Could not detect agentic repo remote" +export SCRIPT_DIR AGENTIC_ROOT AGENTIC_REPO + +echo "" +echo -e "${BOLD}Agentic Client Repo Setup${RESET}" +echo -e "${DIM}Agentic repo: $AGENTIC_REPO${RESET}" + +"$SCRIPT_DIR/prerequisites" + +echo "" + +STEPS="$SCRIPT_DIR/steps" +source "$STEPS/repo" +source "$STEPS/settings" +source "$STEPS/workflows" +source "$STEPS/secrets" +source "$STEPS/collaborator" +source "$STEPS/claude-md" +source "$STEPS/commit-push" + +"$SCRIPT_DIR/diagnostics" "$FULL_REPO" + +cat << EOF + +${BOLD}${GREEN}Setup complete!${RESET} + + Repo: https://github.com/$FULL_REPO + Workflows: $(ls -1 "$AGENTIC_ROOT"/templates/workflows/*.yml | wc -l | tr -d ' ') files in .github/workflows/ + +Flesh out CLAUDE.md with detailed project context, then verify the setup: + + gh issue create --repo $FULL_REPO \\ + --title 'Test agentic setup' --body '@claude Are you there?' +EOF diff --git a/bin/lib/guards.sh b/bin/lib/guards.sh new file mode 100644 index 0000000..e16abf7 --- /dev/null +++ b/bin/lib/guards.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Precondition checks and environment detection + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/output.sh" + +require_vars() { + local missing=() + for var in "$@"; do + [[ -n "${!var:-}" ]] || missing+=("$var") + done + (( ${#missing[@]} == 0 )) || err "Missing required variables: ${missing[*]}" +} + +require_commands() { + for cmd in "$@"; do + command -v "$cmd" &>/dev/null || err "$cmd is not installed" + done +} + +require_gh_auth() { + gh auth status &>/dev/null 2>&1 \ + || err "Not authenticated with GitHub CLI. Run 'gh auth login' first." +} + +detect_remote() { + git -C "$1" remote get-url origin 2>/dev/null \ + | sed -E 's|.*github\.com[:/]||; s|\.git$||' +} diff --git a/bin/lib/output.sh b/bin/lib/output.sh new file mode 100644 index 0000000..0be0478 --- /dev/null +++ b/bin/lib/output.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Colors and message functions for agentic scripts + +BOLD=$'\033[1m' +DIM=$'\033[2m' +GREEN=$'\033[0;32m' +YELLOW=$'\033[0;33m' +RED=$'\033[0;31m' +CYAN=$'\033[0;36m' +RESET=$'\033[0m' + +info() { echo -e "${CYAN}$*${RESET}"; } +ok() { echo -e "${GREEN}✓ $*${RESET}"; } +warn() { echo -e "${YELLOW}$*${RESET}"; } +err() { echo -e "${RED}Error: $*${RESET}" >&2; exit 1; } diff --git a/bin/prerequisites b/bin/prerequisites new file mode 100755 index 0000000..d522910 --- /dev/null +++ b/bin/prerequisites @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENTIC_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +source "$SCRIPT_DIR/lib/output.sh" + +# .env has both tokens — skip the prerequisites prompt +# (bot name and email are derived from the token automatically) +if [[ -f "$AGENTIC_ROOT/.env" ]]; then + source "$AGENTIC_ROOT/.env" + if [[ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]] \ + && [[ -n "${AGENTIC_BOT_TOKEN:-}" ]]; then + ok "Prerequisites satisfied (.env found)" + exit 0 + fi +fi + +# Non-interactive with no .env — can't prompt +if [[ -n "${NONINTERACTIVE:-}" ]]; then + warn "Skipping prerequisites check (non-interactive mode)" + exit 0 +fi + +cat < Developer settings > Personal access tokens > Fine-grained tokens > Generate new token + - Token name: anything you like (e.g. agentic-bot) — just a label for your reference + - Resource owner: set this to your ORG, not the bot's personal account + (a token scoped to the bot user can't access org repos, even if the bot is a member) + One PAT covers all repos in that org. You'd need a separate PAT for a + different org or for personal repos. + - After creating the token, an org admin must approve it: + Org settings > Personal access tokens > Pending requests + - Repository access: All repositories + - Permissions: Contents (read/write), Pull requests (read/write), Issues (read/write), Metadata (read) + + ${BOLD}2. Claude OAuth token${RESET} + Run 'claude setup-token' in your terminal. It authenticates with your + Claude Pro/Max subscription and outputs an OAuth token to copy. + +EOF + +read -rp "Do you have these ready? [y/N]: " PREREQS_READY +[[ "${PREREQS_READY,,}" == "y" ]] || { echo ""; echo -e "${CYAN}Set those up first, then re-run this script.${RESET}"; exit 1; } diff --git a/bin/steps/claude-md b/bin/steps/claude-md new file mode 100755 index 0000000..cf5f6ed --- /dev/null +++ b/bin/steps/claude-md @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars CLIENT_DIR + +echo "" +echo -e "${BOLD}CLAUDE.md${RESET}" +echo "" + +if [[ -f "$CLIENT_DIR/CLAUDE.md" ]]; then + warn "CLAUDE.md already exists — skipping" +elif [[ -n "${NONINTERACTIVE:-}" ]]; then + warn "Skipping CLAUDE.md creation (non-interactive mode)" +else + read -rp "Create a CLAUDE.md template? [Y/n]: " CREATE_CLAUDE_MD + if [[ "${CREATE_CLAUDE_MD,,}" != "n" ]]; then + read -rp "Project name: " PROJECT_NAME + read -rp "Short description: " PROJECT_DESC + read -rp "Tech stack (e.g. Next.js, TypeScript, Prisma): " TECH_STACK + + cat > "$CLIENT_DIR/CLAUDE.md" << MD +# ${PROJECT_NAME:-Project} + +${PROJECT_DESC:-A brief description of what this project does.} + +## Tech stack + +${TECH_STACK:-Describe the technologies used here.} + +## Project structure + +Describe the key directories and files here. + +## Development + +### Setup + +\`\`\`bash +# How to install dependencies and get running locally +\`\`\` + +### Common commands + +\`\`\`bash +# dev server, tests, linting, etc. +\`\`\` + +## Conventions + +- Describe coding conventions, naming patterns, etc. +MD + + ok "Created CLAUDE.md template — edit it to add more project context" + fi +fi diff --git a/bin/steps/collaborator b/bin/steps/collaborator new file mode 100755 index 0000000..a35ea53 --- /dev/null +++ b/bin/steps/collaborator @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars FULL_REPO + +add_collaborator() { + info "Adding $BOT_NAME as collaborator..." + gh api -X PUT "/repos/$FULL_REPO/collaborators/$BOT_NAME" \ + -f permission=write --silent + ok "$BOT_NAME added as collaborator (write access)" +} + +accept_invitation() { + info "Accepting invitation as $BOT_NAME..." + local invite_id + invite_id=$(GH_TOKEN="$BOT_TOKEN" gh api "/user/repository_invitations" \ + --jq ".[] | select(.repository.full_name==\"$FULL_REPO\") | .id" \ + 2>/dev/null || true) + if [[ -n "$invite_id" ]]; then + GH_TOKEN="$BOT_TOKEN" gh api -X PATCH \ + "/user/repository_invitations/$invite_id" --silent + ok "Invitation accepted" + else + warn "Could not find invitation — the bot may need to accept manually at https://github.com/$FULL_REPO/invitations" + fi +} + +if [[ -n "${NONINTERACTIVE:-}" ]]; then + warn "Skipping collaborator setup (non-interactive mode)" + return 0 2>/dev/null || exit 0 +fi + +cat << EOF + +${BOLD}Bot repo access${RESET} + +The bot needs write access to this repo. Two approaches: + +1) Add bot directly as collaborator (simple, per-repo) +2) Skip — I've added the bot to an org team that has repo access + +${DIM}Tip: If you're using a GitHub org, add the bot to a team instead. +Any repo the team can access, the bot automatically can too — +no need to add it individually each time you run this script.${RESET} + +EOF +read -rp "Choose [1/2]: " COLLAB_CHOICE + +if [[ "$COLLAB_CHOICE" == "1" ]]; then + if [[ -z "${BOT_NAME:-}" ]]; then + read -rp "Bot GitHub username: " BOT_NAME + fi + + if [[ -n "$BOT_NAME" ]]; then + add_collaborator + if [[ -n "${BOT_TOKEN:-}" ]]; then + accept_invitation + else + warn "Bot token not available — the bot will need to accept the invitation at https://github.com/$FULL_REPO/invitations" + fi + fi +else + echo "" + warn "Remember to add the bot to the repo via your team/org settings." +fi diff --git a/bin/steps/commit-push b/bin/steps/commit-push new file mode 100755 index 0000000..1c39b57 --- /dev/null +++ b/bin/steps/commit-push @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars CLIENT_DIR + +commit_and_push() { + if git rev-parse HEAD &>/dev/null 2>&1; then + git commit -m "Add agentic workflow files and CLAUDE.md" + git push + else + git checkout -b main + git commit -m "Initial commit: add agentic workflow files and CLAUDE.md" + git push -u origin main + fi +} + +cd "$CLIENT_DIR" +git add .github/workflows/ +[[ -f "CLAUDE.md" ]] && git add CLAUDE.md + +echo "" +if [[ -z "${NONINTERACTIVE:-}" ]]; then + echo -e "${BOLD}Ready to commit and push:${RESET}" + git -c color.status=always status --short + echo "" + read -rp "Push to main? [Y/n]: " CONFIRM_PUSH + if [[ "${CONFIRM_PUSH,,}" == "n" ]]; then + warn "Skipped — files are staged but not committed" + return 0 2>/dev/null || exit 0 + fi +fi + +commit_and_push +ok "Changes committed and pushed" diff --git a/bin/steps/repo b/bin/steps/repo new file mode 100755 index 0000000..2f54a2b --- /dev/null +++ b/bin/steps/repo @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +clone_or_reuse() { + if [[ -d "$REPO_NAME" ]]; then + warn "$REPO_NAME/ already exists locally — reusing it" + else + info "Cloning $FULL_REPO..." + gh repo clone "$FULL_REPO" "$REPO_NAME" + fi + CLIENT_DIR="$(pwd)/$REPO_NAME" +} + +if [[ -n "${FULL_REPO:-}" && -n "${CLIENT_DIR:-}" ]]; then + CLIENT_DIR=$(cd "$CLIENT_DIR" && pwd) || err "Directory not found: $CLIENT_DIR" + ok "Using repo: $FULL_REPO ($CLIENT_DIR)" + export FULL_REPO CLIENT_DIR + return 0 2>/dev/null || exit 0 +fi + +echo "1) Create a new repo" +echo "2) Use an existing repo" +read -rp "Choose [1/2]: " REPO_CHOICE +echo "" + +if [[ "$REPO_CHOICE" == "1" ]]; then + read -rp "Repo name: " REPO_NAME + [[ -z "$REPO_NAME" ]] && err "Repo name cannot be empty" + + echo "" + echo "1) Personal account" + echo "2) Organization" + read -rp "Choose [1/2]: " OWNER_TYPE + + if [[ "$OWNER_TYPE" == "2" ]]; then + read -rp "Organization name: " ORG_NAME + [[ -z "$ORG_NAME" ]] && err "Organization name cannot be empty" + FULL_REPO="$ORG_NAME/$REPO_NAME" + else + OWNER=$(gh api user -q .login) + FULL_REPO="$OWNER/$REPO_NAME" + fi + + echo "" + echo "1) Private" + echo "2) Public" + read -rp "Visibility [1/2]: " VIS_CHOICE + + VISIBILITY="private" + [[ "$VIS_CHOICE" == "2" ]] && VISIBILITY="public" + + echo "" + info "Creating $FULL_REPO ($VISIBILITY)..." + + if [[ "$OWNER_TYPE" == "2" ]]; then + gh repo create "$FULL_REPO" --"$VISIBILITY" + else + gh repo create "$REPO_NAME" --"$VISIBILITY" + fi + + clone_or_reuse + ok "Repo created and cloned to $CLIENT_DIR" + +elif [[ "$REPO_CHOICE" == "2" ]]; then + echo "1) Local clone (enter path)" + echo "2) GitHub repo (enter owner/repo to clone)" + read -rp "Choose [1/2]: " EXISTING_CHOICE + echo "" + + if [[ "$EXISTING_CHOICE" == "1" ]]; then + read -rp "Path to local repo: " CLIENT_DIR_INPUT + CLIENT_DIR=$(cd "$CLIENT_DIR_INPUT" && pwd) || err "Directory not found: $CLIENT_DIR_INPUT" + FULL_REPO=$(detect_remote "$CLIENT_DIR") \ + || err "Could not detect remote for $CLIENT_DIR" + else + read -rp "owner/repo: " FULL_REPO + [[ -z "$FULL_REPO" ]] && err "owner/repo cannot be empty" + REPO_NAME=$(echo "$FULL_REPO" | cut -d/ -f2) + clone_or_reuse + fi + + ok "Using repo: $FULL_REPO ($CLIENT_DIR)" +else + err "Invalid choice" +fi + +export FULL_REPO CLIENT_DIR diff --git a/bin/steps/secrets b/bin/steps/secrets new file mode 100755 index 0000000..daee5b4 --- /dev/null +++ b/bin/steps/secrets @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars FULL_REPO + +print_manual_commands() { + cat << EOF + ${DIM}Run 'claude setup-token' to get your CLAUDE_CODE_OAUTH_TOKEN.${RESET} + + Secrets: + gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo $FULL_REPO + gh secret set AGENTIC_BOT_TOKEN --repo $FULL_REPO + + Variables: + gh variable set AGENTIC_BOT_NAME --repo $FULL_REPO --body "bot-username" + gh variable set AGENTIC_BOT_EMAIL --repo $FULL_REPO --body "123+bot@users.noreply.github.com" +EOF +} + +set_secret() { + echo "$2" | gh secret set "$1" --repo "$FULL_REPO" + ok "$1 set" +} + +set_variable() { + gh variable set "$1" --repo "$FULL_REPO" --body "$2" + ok "$1 = $2" +} + +derive_bot_identity() { + [[ -n "${AGENTIC_BOT_NAME:-}" ]] && [[ -n "${AGENTIC_BOT_EMAIL:-}" ]] && return 0 + info "Looking up bot identity from token..." + local login id + login=$(GH_TOKEN="$1" gh api /user --jq '.login' 2>/dev/null) \ + || err "Could not look up bot identity — check the token is valid" + id=$(GH_TOKEN="$1" gh api /user --jq '.id' 2>/dev/null) + AGENTIC_BOT_NAME="${AGENTIC_BOT_NAME:-$login}" + AGENTIC_BOT_EMAIL="${AGENTIC_BOT_EMAIL:-${id}+${login}@users.noreply.github.com}" + ok "AGENTIC_BOT_NAME = $AGENTIC_BOT_NAME" + ok "AGENTIC_BOT_EMAIL = $AGENTIC_BOT_EMAIL" +} + +all_env_set() { + [[ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]] \ + && [[ -n "${AGENTIC_BOT_TOKEN:-}" ]] \ + && [[ -n "${AGENTIC_BOT_NAME:-}" ]] \ + && [[ -n "${AGENTIC_BOT_EMAIL:-}" ]] +} + +apply_from_env() { + echo "" + info "Setting secrets and variables from environment..." + set_secret CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_CODE_OAUTH_TOKEN" + set_secret AGENTIC_BOT_TOKEN "$AGENTIC_BOT_TOKEN" + set_variable AGENTIC_BOT_NAME "$AGENTIC_BOT_NAME" + set_variable AGENTIC_BOT_EMAIL "$AGENTIC_BOT_EMAIL" + export BOT_TOKEN="$AGENTIC_BOT_TOKEN" BOT_NAME="$AGENTIC_BOT_NAME" +} + +# Derive bot name/email from token if not already set +[[ -n "${AGENTIC_BOT_TOKEN:-}" ]] && derive_bot_identity "$AGENTIC_BOT_TOKEN" + +# All 4 values available — set them directly, no prompts +if all_env_set; then + apply_from_env + return 0 2>/dev/null || exit 0 +fi + +# Non-interactive with missing env vars — can't prompt +if [[ -n "${NONINTERACTIVE:-}" ]]; then + warn "Skipping secrets setup (non-interactive mode)" + print_manual_commands + return 0 2>/dev/null || exit 0 +fi + +echo "" +read -rp "Configure secrets and variables now? [y/N]: " SETUP_SECRETS + +if [[ "${SETUP_SECRETS,,}" == "y" ]]; then + cat << EOF + +${BOLD}Secrets${RESET} +${DIM}Run 'claude setup-token' to get your CLAUDE_CODE_OAUTH_TOKEN. +It authenticates with your Claude Pro/Max subscription and outputs an OAuth token.${RESET} + +EOF + + if [[ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then + set_secret CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_CODE_OAUTH_TOKEN" + else + echo -n "CLAUDE_CODE_OAUTH_TOKEN: " + read -rs CLAUDE_TOKEN + echo "" + [[ -n "$CLAUDE_TOKEN" ]] && set_secret CLAUDE_CODE_OAUTH_TOKEN "$CLAUDE_TOKEN" + fi + + if [[ -n "${AGENTIC_BOT_TOKEN:-}" ]]; then + set_secret AGENTIC_BOT_TOKEN "$AGENTIC_BOT_TOKEN" + BOT_TOKEN="$AGENTIC_BOT_TOKEN" + else + echo -n "AGENTIC_BOT_TOKEN: " + read -rs BOT_TOKEN + echo "" + [[ -n "$BOT_TOKEN" ]] && set_secret AGENTIC_BOT_TOKEN "$BOT_TOKEN" + fi + + echo "" + echo -e "${BOLD}Variables${RESET}" + + # Derive name/email from bot token if available + [[ -n "${BOT_TOKEN:-}" ]] && derive_bot_identity "$BOT_TOKEN" + + if [[ -n "${AGENTIC_BOT_NAME:-}" ]]; then + set_variable AGENTIC_BOT_NAME "$AGENTIC_BOT_NAME" + BOT_NAME="$AGENTIC_BOT_NAME" + else + read -rp "AGENTIC_BOT_NAME: " BOT_NAME + [[ -n "$BOT_NAME" ]] && set_variable AGENTIC_BOT_NAME "$BOT_NAME" + fi + + if [[ -n "${AGENTIC_BOT_EMAIL:-}" ]]; then + set_variable AGENTIC_BOT_EMAIL "$AGENTIC_BOT_EMAIL" + else + read -rp "AGENTIC_BOT_EMAIL: " BOT_EMAIL + [[ -n "$BOT_EMAIL" ]] && set_variable AGENTIC_BOT_EMAIL "$BOT_EMAIL" + fi + + ok "Secrets and variables configured" + + export BOT_TOKEN BOT_NAME +else + echo "" + warn "Remember to set these before using the workflows:" + print_manual_commands +fi diff --git a/bin/steps/settings b/bin/steps/settings new file mode 100755 index 0000000..2903e19 --- /dev/null +++ b/bin/steps/settings @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars FULL_REPO + +echo "" +info "Configuring repo settings..." + +gh api -X PATCH "/repos/$FULL_REPO" \ + -F allow_auto_merge=false \ + -F allow_squash_merge=true \ + -F delete_branch_on_merge=true \ + > /dev/null \ + || err "Failed to update repo settings — check permissions" + +ok "Auto-merge disabled, squash merge enabled, delete branch on merge enabled" diff --git a/bin/steps/workflows b/bin/steps/workflows new file mode 100755 index 0000000..8030f6c --- /dev/null +++ b/bin/steps/workflows @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../lib/guards.sh" + +require_vars CLIENT_DIR AGENTIC_ROOT AGENTIC_REPO + +WORKFLOW_DIR="$CLIENT_DIR/.github/workflows" +TEMPLATE_DIR="$AGENTIC_ROOT/templates/workflows" + +find_existing_workflows() { + EXISTING=() + for template in "$TEMPLATE_DIR"/*.yml; do + local filename=$(basename "$template") + if [[ -f "$WORKFLOW_DIR/$filename" ]]; then + EXISTING+=("$filename") + fi + done +} + +confirm_overwrite() { + warn "These workflow files already exist:" + for f in "${EXISTING[@]}"; do echo -e " ${DIM}$f${RESET}"; done + echo "" + if [[ -n "${NONINTERACTIVE:-}" ]]; then + info "Overwriting (non-interactive mode)" + return 0 + fi + read -rp "Overwrite? [y/N]: " OVERWRITE + [[ "${OVERWRITE,,}" == "y" ]] +} + +render_templates() { + local safe_repo="${AGENTIC_REPO//\\/\\\\}" + safe_repo="${safe_repo//&/\\&}" + for template in "$TEMPLATE_DIR"/*.yml; do + local filename=$(basename "$template") + sed "s|__AGENTIC_REPO__|$safe_repo|g" "$template" > "$WORKFLOW_DIR/$filename" + echo -e " ${DIM}$filename${RESET}" + done +} + +echo "" +info "Writing workflow files..." +mkdir -p "$WORKFLOW_DIR" + +find_existing_workflows +if (( ${#EXISTING[@]} > 0 )); then + confirm_overwrite || { warn "Skipped workflow files"; return 0 2>/dev/null || exit 0; } +fi + +render_templates +ok "Created workflow files in $WORKFLOW_DIR/" diff --git a/templates/workflows/gh-commented.yml b/templates/workflows/gh-commented.yml new file mode 100644 index 0000000..ac59793 --- /dev/null +++ b/templates/workflows/gh-commented.yml @@ -0,0 +1,36 @@ +name: GH Commented + +on: + issue_comment: + types: [created] + issues: + types: [opened] + +jobs: + gh-respond: + if: | + (github.event_name == 'issue_comment' && startsWith(github.event.comment.body, '@claude') && !startsWith(github.event.comment.body, '@claude /pr-start')) || + (github.event_name == 'issues' && startsWith(github.event.issue.body, '@claude')) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + + - uses: __AGENTIC_REPO__/.github/actions/setup-claude-agent@main + + - uses: __AGENTIC_REPO__/.github/actions/run-claude-skill@main + with: + skill-name: gh-respond + context-type: issue + context-number: ${{ github.event.issue.number }} + repository: ${{ github.repository }} + claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + + - uses: __AGENTIC_REPO__/.github/actions/post-comment@main + with: + comment-file: /tmp/comment.txt + issue-or-pr-number: ${{ github.event.issue.number }} + comment-type: issue + repository: ${{ github.repository }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} diff --git a/templates/workflows/gh-start-work.yml b/templates/workflows/gh-start-work.yml new file mode 100644 index 0000000..091cf13 --- /dev/null +++ b/templates/workflows/gh-start-work.yml @@ -0,0 +1,39 @@ +name: GH Start Work + +on: + issue_comment: + types: [created] + +jobs: + pr-start: + if: "!github.event.issue.pull_request && startsWith(github.event.comment.body, '@claude /pr-start')" + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: __AGENTIC_REPO__/.github/actions/setup-claude-agent@main + + - uses: __AGENTIC_REPO__/.github/actions/run-claude-skill@main + with: + skill-name: pr-start + context-type: issue + context-number: ${{ github.event.issue.number }} + repository: ${{ github.repository }} + claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + + - uses: __AGENTIC_REPO__/.github/actions/git-commit-push@main + with: + branch-name-file: /tmp/pr_branch_name.txt + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + bot-name: ${{ vars.AGENTIC_BOT_NAME }} + bot-email: ${{ vars.AGENTIC_BOT_EMAIL }} + + - uses: __AGENTIC_REPO__/.github/actions/create-pr@main + with: + issue-number: ${{ github.event.issue.number }} + repository: ${{ github.repository }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} diff --git a/templates/workflows/pr-approved.yml b/templates/workflows/pr-approved.yml new file mode 100644 index 0000000..9eeb17c --- /dev/null +++ b/templates/workflows/pr-approved.yml @@ -0,0 +1,33 @@ +name: PR Approved + +on: + pull_request_review: + types: [submitted] + +jobs: + pr-merge: + if: github.event.review.state == 'approved' && startsWith(github.event.review.body, '@claude') + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - uses: __AGENTIC_REPO__/.github/actions/setup-claude-agent@main + + - uses: __AGENTIC_REPO__/.github/actions/run-claude-skill@main + with: + skill-name: pr-merge + context-type: pr + context-number: ${{ github.event.pull_request.number }} + repository: ${{ github.repository }} + claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + + - uses: __AGENTIC_REPO__/.github/actions/squash-merge@main + with: + pr-number: ${{ github.event.pull_request.number }} + repository: ${{ github.repository }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} diff --git a/templates/workflows/pr-changes-requested.yml b/templates/workflows/pr-changes-requested.yml new file mode 100644 index 0000000..9ea6d19 --- /dev/null +++ b/templates/workflows/pr-changes-requested.yml @@ -0,0 +1,40 @@ +name: PR Changes Requested + +on: + pull_request_review: + types: [submitted] + +jobs: + pr-fix: + if: github.event.review.state == 'changes_requested' && startsWith(github.event.review.body, '@claude') + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 + + - uses: __AGENTIC_REPO__/.github/actions/setup-claude-agent@main + + - uses: __AGENTIC_REPO__/.github/actions/run-claude-skill@main + with: + skill-name: pr-fix + context-type: pr + context-number: ${{ github.event.pull_request.number }} + repository: ${{ github.repository }} + claude-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + + - uses: __AGENTIC_REPO__/.github/actions/git-commit-push@main + with: + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }} + bot-name: ${{ vars.AGENTIC_BOT_NAME }} + bot-email: ${{ vars.AGENTIC_BOT_EMAIL }} + + - uses: __AGENTIC_REPO__/.github/actions/post-comment@main + with: + issue-or-pr-number: ${{ github.event.pull_request.number }} + comment-type: pr + repository: ${{ github.repository }} + github-token: ${{ secrets.AGENTIC_BOT_TOKEN }}