diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ab9b6e7..178453e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,3 +10,31 @@ Agent instruction files live in `.github/agents/`: - `maintainer-upgrade.md` — how to apply caretaker upgrades Always check these files when you receive a caretaker assignment. + + + +## Caretaker System + +This repository uses the caretaker automated management system. + +### How it works + +- An orchestrator runs weekly via GitHub Actions +- It creates issues and assigns them to @copilot for execution +- When @copilot opens PRs, the orchestrator monitors them through CI, review, and merge +- The orchestrator communicates with @copilot via structured issue/PR comments + +### When assigned an issue by caretaker + +- Read the full issue body carefully — it contains structured instructions +- Follow the instructions exactly as written +- If unclear, comment on the issue asking for clarification +- Always ensure CI passes before considering work complete +- Reference the agent file for your role: `.github/agents/maintainer-pr.md` or `maintainer-issue.md` + +### Conventions + +- Branch naming: `maintainer/{type}-{description}` +- Commit messages: `chore(maintainer): {description}` +- Always run existing tests before pushing +- Do not modify `.github/maintainer/` files unless explicitly instructed diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..0fdb885 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,109 @@ +name: Claude Code Review (gated) + +# DORMANT BY DEFAULT — this workflow does NOT auto-run on PR open/push. +# +# It fires only when something explicitly triggers it: +# 1. Caretaker applies the ``claude-code`` label (happens when +# ``pr_reviewer.complex_reviewer = "claude_code"`` in caretaker +# config and a complex PR is dispatched). +# 2. A human applies the same label manually. +# 3. A human runs the workflow from the Actions tab (workflow_dispatch). +# +# To prefer the in-pod opencode_local backend (no consumer-side workflow +# at all), set in ``.github/maintainer/config.yml``: +# +# pr_reviewer: +# complex_reviewer: opencode_local +# enabled_backends: [claude_code, opencode, opencode_local, pr_agent] +# +# and never apply the ``claude-code`` label. This workflow stays +# present as a fallback / opt-in path; the backend is the default. + +on: + pull_request: + types: [labeled] + workflow_dispatch: + inputs: + pr_number: + description: "PR number to review" + required: true + type: number + +jobs: + claude-review: + # Only run when the claude-code label was just applied, or when + # the workflow was dispatched manually. + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && github.event.label.name == 'claude-code') + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Resolve PR number + id: pr + run: | + if [ -n "${{ github.event.pull_request.number }}" ]; then + echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + else + echo "number=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT" + fi + + - name: Preflight — check Claude OAuth token availability + id: preflight + env: + OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + run: | + if [ -z "$OAUTH_TOKEN" ]; then + echo "::warning::CLAUDE_CODE_OAUTH_TOKEN is not set — falling back to Copilot review" + echo "claude_available=false" >> "$GITHUB_OUTPUT" + else + echo "claude_available=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run Claude Code Review + id: claude-review + if: steps.preflight.outputs.claude_available == 'true' + continue-on-error: true + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + plugin_marketplaces: 'https://github.com/anthropics/claude-code.git' + plugins: 'code-review@claude-code-plugins' + prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ steps.pr.outputs.number }}' + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + + - name: Fallback — request Copilot review + if: | + steps.preflight.outputs.claude_available != 'true' || + steps.claude-review.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const prNumber = parseInt('${{ steps.pr.outputs.number }}', 10); + const reason = '${{ steps.preflight.outputs.claude_available }}' === 'true' + ? 'Claude review action failed' + : 'Claude OAuth token unavailable'; + core.info(`${reason} — requesting Copilot review for PR #${prNumber}`); + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: ['copilot-pull-request-reviewer'], + }); + core.info(`Requested Copilot review for PR #${prNumber}`); + } catch (error) { + core.warning(`Copilot review request failed: ${error.message}`); + } diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml new file mode 100644 index 0000000..0a5f8da --- /dev/null +++ b/.github/workflows/opencode-review.yml @@ -0,0 +1,119 @@ +name: opencode Review (gated) + +# DORMANT BY DEFAULT — this workflow does NOT auto-run on PR open/push. +# +# It fires only when something explicitly triggers it: +# 1. Caretaker applies the ``opencode-review`` label (happens when +# ``pr_reviewer.complex_reviewer = "opencode"`` in caretaker config +# and a complex PR is dispatched). +# 2. A human applies the same label manually. +# 3. A human runs the workflow from the Actions tab (workflow_dispatch). +# +# To prefer the in-pod opencode_local backend (no consumer-side workflow +# at all), set in ``.github/maintainer/config.yml``: +# +# pr_reviewer: +# complex_reviewer: opencode_local +# enabled_backends: [claude_code, opencode, opencode_local, pr_agent] +# +# and never apply the ``opencode-review`` label. This workflow stays +# present as a fallback / opt-in path; the backend is the default. + +on: + pull_request: + types: [labeled] + workflow_dispatch: + inputs: + pr_number: + description: "PR number to review" + required: true + type: number + +jobs: + opencode-review: + # Only run when the opencode-review label was just applied, or + # when the workflow was dispatched manually. + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && github.event.label.name == 'opencode-review') + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Resolve PR number + id: pr + run: | + if [ -n "${{ github.event.pull_request.number }}" ]; then + echo "number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT" + else + echo "number=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT" + fi + + - name: Preflight — check opencode provider keys are available + id: preflight + env: + ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENROUTER_KEY: ${{ secrets.OPENROUTER_API_KEY }} + GROQ_KEY: ${{ secrets.GROQ_API_KEY }} + run: | + if [ -z "$ANTHROPIC_KEY" ] && [ -z "$OPENAI_KEY" ] && [ -z "$OPENROUTER_KEY" ] && [ -z "$GROQ_KEY" ]; then + echo "::warning::No opencode provider key set — falling back to Copilot review" + echo "opencode_available=false" >> "$GITHUB_OUTPUT" + else + echo "opencode_available=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run opencode review + id: opencode-review + if: steps.preflight.outputs.opencode_available == 'true' + continue-on-error: true + # Pinning to ``@latest`` is convenient but a supply-chain risk. + # Pin to a specific tag (``sst/opencode/github@v0.1.0``) or + # commit SHA before relying on this in production. + uses: sst/opencode/github@latest + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} + GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} + with: + model: anthropic/claude-sonnet-4 + prompt: | + Review pull request ${{ github.repository }}#${{ steps.pr.outputs.number }}. + Focus on correctness, security, API contracts, and missing tests. + Post a review comment summary and inline comments where applicable. + + - name: Fallback — request Copilot review + if: | + steps.preflight.outputs.opencode_available != 'true' || + steps.opencode-review.outcome == 'failure' + uses: actions/github-script@v7 + with: + script: | + const prNumber = parseInt('${{ steps.pr.outputs.number }}', 10); + const reason = '${{ steps.preflight.outputs.opencode_available }}' === 'true' + ? 'opencode review action failed' + : 'opencode provider keys unavailable'; + core.info(`${reason} — requesting Copilot review for PR #${prNumber}`); + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + reviewers: ['copilot-pull-request-reviewer'], + }); + core.info(`Requested Copilot review for PR #${prNumber}`); + } catch (error) { + core.warning(`Copilot review request failed: ${error.message}`); + }