name: '🧠 Gemini CLI Bot: Brain' on: schedule: - cron: '0 0 * * *' # Every 24 hours issue_comment: types: ['created'] workflow_dispatch: inputs: run_interactive: description: 'Run interactive flow (requires issue_number)' type: 'boolean' default: false issue_number: description: 'Issue/PR number to simulate context from' type: 'string' required: false comment_id: description: 'Specific comment ID to simulate' type: 'string' required: false clear_memory: description: 'Clear memory (drops learnings from previous runs)' type: 'boolean' default: false enable_prs: description: 'Enable PRs (automatically promote changes to PRs)' type: 'boolean' default: false concurrency: group: '${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.event.inputs.issue_number || github.ref }}' cancel-in-progress: true jobs: reasoning: name: 'Brain (Reasoning Layer)' runs-on: 'ubuntu-latest' if: | github.repository == 'google-gemini/gemini-cli' && ( github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_interactive != 'true') || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_interactive == 'true') || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@gemini-cli-robot') && contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association)) ) # The reasoning phase is strictly readonly. permissions: contents: 'read' issues: 'read' pull-requests: 'read' actions: 'read' env: GEMINI_CLI_TRUST_WORKSPACE: 'true' steps: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 with: fetch-depth: 0 persist-credentials: false - name: 'Setup Node.js' uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: 'Install dependencies' run: 'npm ci' - name: 'Build Gemini CLI' run: 'npm run bundle' - name: 'Download Previous State' env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: | if [ "${{ github.event.inputs.clear_memory }}" = "true" ]; then echo "Memory clear requested. Skipping previous state download." exit 0 fi # Find the last successful run of this workflow LAST_RUN_ID=$(gh run list --workflow "${{ github.workflow }}" --status success --limit 1 --json databaseId --jq '.[0].databaseId') if [ -n "$LAST_RUN_ID" ]; then echo "Found previous successful run: $LAST_RUN_ID" # Download brain memory (all state in one artifact) gh run download "$LAST_RUN_ID" -n brain-data -D . || echo "brain-data not found" else echo "No previous successful run found." fi - name: 'Collect Current Metrics' env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' run: 'npx tsx tools/gemini-cli-bot/metrics/index.ts' - name: 'Run Brain Phases' env: GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' GEMINI_MODEL: 'gemini-3-flash-preview' ENABLE_PRS: "${{ github.event.inputs.enable_prs || 'false' }}" TRIGGER_ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}' TRIGGER_COMMENT_ID: '${{ github.event.comment.id || github.event.inputs.comment_id }}' run: | PROMPT_PATH="tools/gemini-cli-bot/brain/metrics.md" if [ "${{ github.event_name }}" = "issue_comment" ] || [ "${{ github.event.inputs.run_interactive }}" = "true" ]; then PROMPT_PATH="tools/gemini-cli-bot/brain/interactive.md" export ENABLE_PRS="true" fi touch trigger_context.md if [ -n "$TRIGGER_ISSUE_NUMBER" ]; then echo "" > trigger_context.md echo "# Interactive Trigger Context" >> trigger_context.md echo "You were invoked by a user in issue/PR #$TRIGGER_ISSUE_NUMBER." >> trigger_context.md if [ -n "$TRIGGER_COMMENT_ID" ]; then echo "## User Comment" >> trigger_context.md gh api "repos/${{ github.repository }}/issues/comments/$TRIGGER_COMMENT_ID" -q '.body' >> trigger_context.md echo "" >> trigger_context.md fi echo "## Issue/PR Context" >> trigger_context.md gh issue view "$TRIGGER_ISSUE_NUMBER" >> trigger_context.md 2>/dev/null || gh pr view "$TRIGGER_ISSUE_NUMBER" >> trigger_context.md echo "" >> trigger_context.md fi cat trigger_context.md "$PROMPT_PATH" tools/gemini-cli-bot/brain/common.md > combined_prompt.md node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml -p "$(cat combined_prompt.md)" - name: 'Run Critique Phase' if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}" env: GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' GEMINI_MODEL: 'gemini-3-flash-preview' run: | if git diff --staged --quiet; then echo "No changes staged. Skipping critique." echo "[APPROVED]" > critique_result.txt else node bundle/gemini.js --policy tools/gemini-cli-bot/ci-policy.toml -p "$(cat tools/gemini-cli-bot/brain/critique.md)" 2>&1 | tee critique_output.log if [ "${PIPESTATUS[0]}" -eq 0 ] && grep -q "\[APPROVED\]" critique_output.log && ! grep -q "\[REJECTED\]" critique_output.log; then echo "[APPROVED]" > critique_result.txt else echo "Critique failed, rejected, or did not explicitly approve changes. Skipping PR creation." echo "[REJECTED]" > critique_result.txt fi fi - name: 'Generate Patch' if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}" run: | touch bot-changes.patch touch pr-description.md if [ -f critique_result.txt ] && grep -q "\[APPROVED\]" critique_result.txt && ! grep -q "\[REJECTED\]" critique_result.txt; then git diff --staged > bot-changes.patch else echo "Critique did not approve. Skipping patch generation." fi - name: 'Archive Brain Data' uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4 with: name: 'brain-data' path: | tools/gemini-cli-bot/lessons-learned.md tools/gemini-cli-bot/history/*.csv bot-changes.patch pr-description.md branch-name.txt pr-comment.md pr-number.txt issue-comment.md retention-days: 90 publish: name: 'Publish Artifacts (Archive Layer)' needs: 'reasoning' runs-on: 'ubuntu-latest' if: "github.repository == 'google-gemini/gemini-cli'" # The publish phase is for archiving artifacts and optionally creating PRs. permissions: contents: 'write' pull-requests: 'write' actions: 'write' steps: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 with: ref: 'main' fetch-depth: 0 persist-credentials: false - name: 'Download Brain Data' uses: 'actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093' # ratchet:actions/download-artifact@v4 with: name: 'brain-data' path: '${{ runner.temp }}/brain-data/' - name: 'Create or Update PR' if: "${{ github.event.inputs.enable_prs == 'true' || github.event_name == 'issue_comment' || github.event.inputs.run_interactive == 'true' }}" env: GH_TOKEN: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}' run: | if [ -s "${{ runner.temp }}/brain-data/bot-changes.patch" ]; then git config user.name "gemini-cli-robot" git config user.email "gemini-cli-robot@google.com" git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" BRANCH_NAME="bot/productivity-updates-$(date +'%Y%m%d%H%M%S')-${{ github.run_id }}" if [ -f "${{ runner.temp }}/brain-data/branch-name.txt" ]; then BRANCH_NAME=$(cat "${{ runner.temp }}/brain-data/branch-name.txt") fi if [[ ! "$BRANCH_NAME" =~ ^bot/ ]]; then echo "Error: Branch name '$BRANCH_NAME' does not start with 'bot/'. Safety abort." exit 1 fi git checkout -b "$BRANCH_NAME" git apply "${{ runner.temp }}/brain-data/bot-changes.patch" git add . if [ -s "${{ runner.temp }}/brain-data/pr-description.md" ]; then git commit -F "${{ runner.temp }}/brain-data/pr-description.md" else git commit -m "🤖 Gemini Bot Productivity Optimizations" fi git push origin "$BRANCH_NAME" --force PR_TITLE="🤖 Gemini Bot Productivity Optimizations" if [ -s "${{ runner.temp }}/brain-data/pr-description.md" ]; then PR_TITLE=$(head -n 1 "${{ runner.temp }}/brain-data/pr-description.md") fi if ! gh pr view "$BRANCH_NAME" > /dev/null 2>&1; then gh pr create --draft --title "$PR_TITLE" --body-file "${{ runner.temp }}/brain-data/pr-description.md" --head "$BRANCH_NAME" --base main || \ gh pr create --draft --title "🤖 Gemini Bot Productivity Optimizations" --body "Automated changes generated by Gemini CLI Bot." --head "$BRANCH_NAME" --base main fi fi - name: 'Post PR/Issue Comment' env: GH_TOKEN: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}' TRIGGER_ISSUE_NUMBER: '${{ github.event.issue.number || github.event.inputs.issue_number }}' run: | if [ -s "${{ runner.temp }}/brain-data/issue-comment.md" ] && [ -n "$TRIGGER_ISSUE_NUMBER" ]; then echo "Posting comment to triggering issue #$TRIGGER_ISSUE_NUMBER" gh issue comment "$TRIGGER_ISSUE_NUMBER" -F "${{ runner.temp }}/brain-data/issue-comment.md" fi if [ -s "${{ runner.temp }}/brain-data/pr-comment.md" ] && [ -f "${{ runner.temp }}/brain-data/pr-number.txt" ]; then PR_NUM=$(cat "${{ runner.temp }}/brain-data/pr-number.txt") PR_AUTHOR=$(gh pr view "$PR_NUM" --json author --jq '.author.login') if [ "$PR_AUTHOR" != "gemini-cli-robot" ]; then echo "Error: PR #$PR_NUM is authored by '$PR_AUTHOR', not 'gemini-cli-robot'. Safety abort." exit 1 fi gh pr comment "$PR_NUM" -F "${{ runner.temp }}/brain-data/pr-comment.md" fi