Skip to content

Commit 6915407

Browse files
authored
Add optional planning step to gemini-cli workflow (#24)
The Gemini CLI workflow has been enhanced with an optional planning step. This feature allows the AI to propose a plan of action for user approval before executing potentially destructive or complex tasks, such as modifying files. --------- Signed-off-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
1 parent c1a873f commit 6915407

3 files changed

Lines changed: 422 additions & 99 deletions

File tree

.github/workflows/gemini-cli.yml

Lines changed: 200 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ on:
1414
concurrency:
1515
group: '${{ github.workflow }}-${{ github.event.issue.number }}'
1616
cancel-in-progress: |-
17-
${{ github.event.sender.type == 'User' && ( github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR') }}
17+
${{ github.event.sender.type == 'User' && ( github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR') }}
1818
1919
defaults:
2020
run:
@@ -33,7 +33,10 @@ jobs:
3333
github.event_name == 'workflow_dispatch' ||
3434
(
3535
github.event_name == 'issues' && github.event.action == 'opened' &&
36-
contains(github.event.issue.body, '@gemini-cli') &&
36+
(
37+
contains(github.event.issue.body, '@gemini-cli') ||
38+
contains(github.event.issue.body, 'plan#')
39+
) &&
3740
!contains(github.event.issue.body, '/review') &&
3841
!contains(github.event.issue.body, '/triage') &&
3942
(
@@ -46,7 +49,10 @@ jobs:
4649
) ||
4750
(
4851
github.event_name == 'issue_comment' &&
49-
contains(github.event.comment.body, '@gemini-cli') &&
52+
(
53+
contains(github.event.comment.body, '@gemini-cli') ||
54+
contains(github.event.comment.body, 'plan#')
55+
) &&
5056
!contains(github.event.comment.body, '/review') &&
5157
!contains(github.event.comment.body, '/triage') &&
5258
(
@@ -59,7 +65,10 @@ jobs:
5965
) ||
6066
(
6167
github.event_name == 'pull_request_review' &&
62-
contains(github.event.review.body, '@gemini-cli') &&
68+
(
69+
contains(github.event.review.body, '@gemini-cli') ||
70+
contains(github.event.review.body, 'plan#')
71+
) &&
6372
!contains(github.event.review.body, '/review') &&
6473
!contains(github.event.review.body, '/triage') &&
6574
(
@@ -72,7 +81,10 @@ jobs:
7281
) ||
7382
(
7483
github.event_name == 'pull_request_review_comment' &&
75-
contains(github.event.comment.body, '@gemini-cli') &&
84+
(
85+
contains(github.event.comment.body, '@gemini-cli') ||
86+
contains(github.event.comment.body, 'plan#')
87+
) &&
7688
!contains(github.event.comment.body, '/review') &&
7789
!contains(github.event.comment.body, '/triage') &&
7890
(
@@ -101,12 +113,18 @@ jobs:
101113
env:
102114
EVENT_NAME: '${{ github.event_name }}'
103115
EVENT_PAYLOAD: '${{ toJSON(github.event) }}'
116+
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
117+
REPOSITORY: '${{ github.repository }}'
104118
run: |-
105119
set -euo pipefail
106120
107121
USER_REQUEST=""
108122
ISSUE_NUMBER=""
109123
IS_PR="false"
124+
REQUEST_TYPE="initial_request"
125+
PLAN_ID=""
126+
PLAN_TEXT=""
127+
COMMENT_ID=""
110128
111129
if [[ "${EVENT_NAME}" == "issues" ]]; then
112130
USER_REQUEST=$(echo "${EVENT_PAYLOAD}" | jq -r .issue.body)
@@ -127,15 +145,65 @@ jobs:
127145
IS_PR="true"
128146
fi
129147
148+
# Check for plan-related comments
149+
if [[ ${USER_REQUEST} == *"plan#"* ]]; then
150+
PLAN_ID=$(echo "${USER_REQUEST}" | grep -o 'plan#[a-f0-9-]*' | sed 's/plan#//')
151+
if [[ ${USER_REQUEST} == *"approved"* ]]; then
152+
REQUEST_TYPE="plan_execution"
153+
elif [[ ${USER_REQUEST} == *"rejected"* ]]; then
154+
REQUEST_TYPE="plan_rejection"
155+
else
156+
REQUEST_TYPE="plan_modification"
157+
fi
158+
159+
if [[ "${REQUEST_TYPE}" == "plan_execution" || "${REQUEST_TYPE}" == "plan_modification" ]]; then
160+
PLAN_COMMENT_DATA=$(gh issue view "${ISSUE_NUMBER}" --repo "${REPOSITORY}" --json comments --jq "[.comments[] | select(.body | contains(\"plan#${PLAN_ID}\"))] | .[0]")
161+
PLAN_TEXT=$(echo "${PLAN_COMMENT_DATA}" | jq -r .body)
162+
COMMENT_ID=$(echo "${PLAN_COMMENT_DATA}" | jq -r .id)
163+
fi
164+
fi
165+
130166
# Clean up user request
131-
USER_REQUEST=$(echo "${USER_REQUEST}" | sed 's/.*@gemini-cli//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
167+
if [[ ${USER_REQUEST} == *"@gemini-cli"* ]]; then
168+
USER_REQUEST=$(echo "${USER_REQUEST}" | sed 's/.*@gemini-cli//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
169+
fi
170+
171+
if [[ "${IS_PR}" == "true" ]]; then
172+
COMMENT_COMMAND="gh pr comment \"${ISSUE_NUMBER}\" --body-file response.md"
173+
else
174+
COMMENT_COMMAND="gh issue comment \"${ISSUE_NUMBER}\" --body-file response.md"
175+
fi
176+
177+
COMMENT_PROGRESS_COMMAND=""
178+
if [[ -n "${COMMENT_ID}" ]]; then
179+
if [[ "${IS_PR}" == "true" ]]; then
180+
COMMENT_PROGRESS_COMMAND="gh pr comment --edit \"${COMMENT_ID}\" --body-file response.md"
181+
else
182+
COMMENT_PROGRESS_COMMAND="gh issue comment --edit \"${COMMENT_ID}\" --body-file response.md"
183+
fi
184+
fi
132185
133186
{
134-
echo "user_request=${USER_REQUEST}"
187+
echo "user_request<<EOF"
188+
echo "${USER_REQUEST}"
189+
echo "EOF"
135190
echo "issue_number=${ISSUE_NUMBER}"
136191
echo "is_pr=${IS_PR}"
192+
echo "request_type=${REQUEST_TYPE}"
193+
echo "plan_id=${PLAN_ID}"
194+
echo "comment_id=${COMMENT_ID}"
195+
echo "comment_command=${COMMENT_COMMAND}"
196+
echo "comment_progress_command=${COMMENT_PROGRESS_COMMAND}"
197+
echo "plan_text<<EOF"
198+
echo "${PLAN_TEXT}"
199+
echo "EOF"
137200
} >> "${GITHUB_OUTPUT}"
138201
202+
- name: 'Set up git user for commits'
203+
run: |
204+
git config --global user.name 'gemini-cli[bot]'
205+
git config --global user.email 'gemini-cli[bot]@users.noreply.github.com'
206+
139207
- name: 'Checkout PR branch'
140208
if: |-
141209
${{ steps.get_context.outputs.is_pr == 'true' }}
@@ -155,24 +223,60 @@ jobs:
155223
repository: '${{ github.repository }}'
156224
fetch-depth: 0
157225

226+
- name: 'Create new branch for issue'
227+
if: |-
228+
${{ steps.get_context.outputs.is_pr == 'false' }}
229+
run: |
230+
set -euo pipefail
231+
BRANCH_NAME="gemini-issue-${{ steps.get_context.outputs.issue_number }}"
232+
git checkout -b "${BRANCH_NAME}"
233+
echo "branch_name=${BRANCH_NAME}" >> "${GITHUB_OUTPUT}"
234+
id: 'create_branch'
235+
158236
- name: 'Acknowledge request'
159237
env:
160238
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
161239
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
162240
REPOSITORY: '${{ github.repository }}'
163-
run: |-
164-
gh issue comment "${ISSUE_NUMBER}" \
165-
--body "I've received your request and I'm working on it now! 🤖" \
166-
--repo "${REPOSITORY}"
241+
REQUEST_TYPE: '${{ steps.get_context.outputs.request_type }}'
242+
run: |
243+
set -euo pipefail
244+
MESSAGE=""
245+
case "${REQUEST_TYPE}" in
246+
'initial_request')
247+
MESSAGE="I've received your request and I'm working on it now! 🤖"
248+
;;
249+
'plan_modification')
250+
MESSAGE="I've received your modification request and I'm working on a new plan! 📝"
251+
;;
252+
'plan_rejection')
253+
MESSAGE="I've received your rejection and will respond shortly. 🤔"
254+
;;
255+
esac
256+
if [[ -n "${MESSAGE}" ]]; then
257+
gh issue comment "${ISSUE_NUMBER}" \
258+
--body "${MESSAGE}" \
259+
--repo "${REPOSITORY}"
260+
fi
167261
168262
- name: 'Run Gemini'
263+
id: 'run_gemini'
169264
uses: './'
170265
env:
171266
GITHUB_TOKEN: '${{ steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }}'
172267
REPOSITORY: '${{ github.repository }}'
173-
USER_REQUEST: '${{ steps.get_context.outputs.user_request }}'
268+
USER_REQUEST: '${{ steps.get_context.outputs.user_request }}\nPlease respond to me by commenting your response. Please execute ${{ steps.get_context.outputs.comment_command }} to respond.'
174269
ISSUE_NUMBER: '${{ steps.get_context.outputs.issue_number }}'
175270
IS_PR: '${{ steps.get_context.outputs.is_pr }}'
271+
REQUEST_TYPE: '${{ steps.get_context.outputs.request_type }}'
272+
PLAN_ID: '${{ steps.get_context.outputs.plan_id }}'
273+
PLAN_TEXT: '${{ steps.get_context.outputs.plan_text }}'
274+
COMMENT_ID: '${{ steps.get_context.outputs.comment_id }}'
275+
BRANCH_NAME: '${{ steps.create_branch.outputs.branch_name }}'
276+
COMMENT_COMMAND: '${{ steps.get_context.outputs.comment_command }}'
277+
COMMENT_PROGRESS_COMMAND: '${{ steps.get_context.outputs.comment_progress_command }}'
278+
OTLP_GOOGLE_CLOUD_PROJECT: '${{ vars.OTLP_GOOGLE_CLOUD_PROJECT }}'
279+
SERVICE_ACCOUNT_EMAIL: '${{ vars.SERVICE_ACCOUNT_EMAIL }}'
176280
with:
177281
gemini_api_key: '${{ secrets.GEMINI_API_KEY }}'
178282
gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}'
@@ -211,47 +315,99 @@ jobs:
211315
},
212316
"sandbox": false
213317
}
214-
prompt: |
215-
## Role
318+
prompt: |-
319+
## Role & Goal
216320
217-
You are a helpful AI assistant invoked via a CLI interface in a GitHub workflow.
218-
You have access to tools to interact with the repository and respond to the user.
321+
You are an AI assistant in a GitHub workflow. Your goal is to understand a user's request, and either answer it directly or create a plan to fulfill it. You will interact with the user by posting comments to the GitHub issue or pull request.
219322
220323
## Context
221324
222-
- **Repository**: `${{ github.repository }}`
223-
- **Triggering Event**: `${{ github.event_name }}`
224-
- **Issue/PR Number**: `${{ steps.get_context.outputs.issue_number }}`
225-
- **Is this a PR?**: `${{ steps.get_context.outputs.is_pr }}`
325+
- **Repository**: '${{ github.repository }}'
326+
- **Triggering Event**: '${{ github.event_name }}'
327+
- **Issue/PR Number**: '${{ env.ISSUE_NUMBER }}'
328+
- **Is this a PR?**: '${{ env.IS_PR }}'
329+
- **Branch Name**: '${{ env.BRANCH_NAME }}'
330+
- **User Request**: '${{ env.USER_REQUEST }}'
331+
- **Request Type**: '${{ env.REQUEST_TYPE }}'
332+
- **Plan Text**: '${{ env.PLAN_TEXT }}'
333+
- **Comment Command**: '${{ env.COMMENT_COMMAND }}'
334+
- **Comment Progress Command**: '${{ env.COMMENT_PROGRESS_COMMAND }}'
335+
336+
## Instructions by Request Type
337+
338+
Your action depends on the `REQUEST_TYPE`.
339+
340+
### 1. `initial_request`
341+
342+
- **Analyze the user's request.**
343+
- **If the request is a question or asks for information (a "simple" request):**
344+
- Gather the information using your tools.
345+
- Write your final answer to `response.md`.
346+
- **If the request requires changing the codebase or running commands that modify state (a "complex" request):**
347+
- You MUST create a plan for the user to approve.
348+
- **CRITICAL: Do not execute the plan. Your ONLY task is to create the plan.**
349+
- Generate a unique UUID for the plan.
350+
- Create the plan in a file named `response.md` using the exact format below.
351+
#### Plan Format
352+
```markdown
353+
plan#<your-uuid-here>
354+
### Plan Overview
355+
I will perform the following actions to address your request:
356+
- *Briefly describe the overall goal of the plan.*
357+
### Detailed Steps
358+
- [ ] **Step 1 Title**: Description of what will be done in this step.
359+
- [ ] **Step 2 Title**: Description of what will be done in this step.
360+
- [ ] ...
361+
To approve this plan, please respond with:
362+
```bash
363+
@gemini-cli plan#<your-uuid-here> approved
364+
```
365+
To request a modification, please respond with:
366+
```bash
367+
@gemini-cli plan#<your-uuid-here> <your modification request>
368+
```
369+
```
370+
- **After creating your response, execute the command in `COMMENT_COMMAND` to post it. Your job for this workflow run is complete.**
226371
227-
## User Request
372+
### 2. `plan_execution`
228373
229-
The user has sent the following request:
230-
`${{ steps.get_context.outputs.user_request }}`
374+
- The user has approved the plan. The approved plan is in the `PLAN_TEXT` variable.
375+
- **Execute the steps from the plan and report progress.** As you make progress, keep the checklist visible and up to date by editing the same comment (check off completed tasks with `- [x]`).
376+
- To report progress, write the updated plan to `response.md` and execute the command in `COMMENT_PROGRESS_COMMAND`.
377+
- If you make code changes:
378+
- **CRITICAL: NEVER commit directly to the `main` branch.**
379+
- Commit your changes to the currently checked-out branch.
380+
- If `Is this a PR?` is `true`, commit to the PR branch.
381+
- If `Is this a PR?` is `false`, commit to the new branch: '${{ env.BRANCH_NAME }}'.
382+
- Stage and commit your changes with a descriptive commit message:
383+
- `git add path/to/file.js`
384+
- `git commit -m "<describe the fix>"`
385+
- `git push origin "${{ env.BRANCH_NAME }}"`
386+
- If a new branch was created for an issue, create a pull request:
387+
- `gh pr create --title "Resolves #${{ env.ISSUE_NUMBER }}" --body "This PR was created by @gemini-cli to address issue #${{ env.ISSUE_NUMBER }}."`
388+
- After all steps are complete, summarize what was changed and why in `response.md`, then execute the command in `COMMENT_COMMAND` to post a final summary comment.
231389
232-
## Steps
390+
### 3. `plan_modification`
233391
234-
1. **Understand the context.** Use the available tools to gather information about the issue or PR.
235-
- For PRs, you can use `gh pr view "${ISSUE_NUMBER}"`, `gh pr diff "${ISSUE_NUMBER}"` and `gh pr list`.
236-
- For issues, you can use `gh issue view "${ISSUE_NUMBER}"` or `gh issue list` to list all issues.
237-
- To view file contents, use `cat`, `head`, or `tail`.
238-
- The code has been checked out for you if this is a PR.
392+
- The user has requested changes to the plan in `PLAN_TEXT`. The requested changes are in `USER_REQUEST`.
393+
- Create a *new* plan that incorporates the user's feedback.
394+
- Generate a *new* unique UUID for this revised plan.
395+
- Write the new plan to `response.md`, then execute the command in `COMMENT_COMMAND` to post it.
239396
240-
2. **Fulfill the user's request.** The request is: `${{ steps.get_context.outputs.user_request }}`.
241-
- If the request involves modifying code, use `write_file` or other tools to make the changes.
242-
- After making changes, you **MUST** commit them.
243-
- `git add .`
244-
- `git commit -m "Your descriptive commit message"`
245-
- `git push`
397+
### 4. `plan_rejection`
246398
247-
3. **Respond to the user.**
248-
- Write your final response in a markdown file: `write_file("response.md", "<your response here>")`
249-
- Post the response as a comment:
250-
- For PRs: `gh pr comment "${ISSUE_NUMBER}" --body-file response.md`
251-
- For Issues: `gh issue comment "${ISSUE_NUMBER}" --body-file response.md`
399+
- The user has rejected the plan.
400+
- Write a confirmation message to `response.md`, then execute the command in `COMMENT_COMMAND` to post it.
252401
253-
## Guidelines
402+
## General Rules
254403
255-
- **Be concise.** Provide the information or perform the action requested without unnecessary chatter.
256-
- **Reference all shell variables as `"${VAR}"`** (with quotes and braces) to prevent errors.
257-
- **If you make changes, always commit and push them.**
404+
- **If you are unsure how to proceed or the user's request is ambiguous, you MUST ask for clarification.** Do not guess. Write your question in `response.md` and post it.
405+
- **Use markdown** for clear formatting in all your responses.
406+
- **Resource Limits**: You MUST NOT propose a plan that creates an excessive number of resources (e.g., more than 5 issues, more than 5 pull requests) in a single request.
407+
- **Malicious Intent**: If you suspect a user request is malicious, frivolous, or intended to abuse the system (e.g., asking to perform a repetitive, useless task), you MUST reject the request and explain why.
408+
- **Guardrail**: Only propose plans that modify files if the user explicitly asks for a change. If they ask a question, just answer it.
409+
- **Commits**: When committing files, be specific (e.g., `git add path/to/file.js`). Never use `git add .`.
410+
- **Paths**: Always use absolute paths for file operations.
411+
- The file `response.md` MUST NEVER be committed.
412+
- **CRITICAL RULE: NEVER, under any circumstances, commit directly to the `main` branch.**
413+
- **CRITICAL RULE: ALWAYS respond to the user by executing `COMMENT_COMMAND`.**

workflows/gemini-cli/README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,30 +83,41 @@ The workflow follows a clear, multi-step process to handle requests:
8383
flowchart TD
8484
subgraph "User Interaction"
8585
A[User posts comment with '@gemini-cli <request>']
86+
F{Approve plan?}
8687
end
8788
8889
subgraph "Gemini CLI Workflow"
8990
B[Acknowledge Request]
9091
C[Checkout Code]
91-
D[Run Gemini with Tools]
92-
E{Request involves code changes?}
93-
F[Commit and Push Changes]
94-
G[Post Final Response]
92+
D[Run Gemini]
93+
E{Is a plan required?}
94+
G[Post Plan for Approval]
95+
H[Execute Request]
96+
I{Request involves code changes?}
97+
J[Commit and Push Changes]
98+
K[Post Final Response]
9599
end
96100
97101
A --> B
98102
B --> C
99103
C --> D
100104
D --> E
101-
E -- Yes --> F
102-
F --> G
103-
E -- No --> G
105+
E -- Yes --> G
106+
G --> F
107+
F -- Yes --> H
108+
F -- No --> K
109+
E -- No --> H
110+
H --> I
111+
I -- Yes --> J
112+
J --> K
113+
I -- No --> K
104114
```
105115

106-
1. **Acknowledge**: The Gemini CLI GitHub Action first posts a brief comment to let the user know the request has been received.
107-
2. **Execute**: The workflow runs the Gemini AI model, providing it with the user's request, repository context, and a set of tools.
108-
3. **Commit (if needed)**: If the Gemini AI model (via the Gemini CLI) uses tools to modify files, it will automatically commit and push the changes to the branch.
109-
4. **Respond**: The Gemini AI posts a final, comprehensive response as a comment on the issue or pull request.
116+
1. **Acknowledge**: The action first posts a brief comment to let the user know the request has been received.
117+
2. **Plan (if needed)**: For requests that may involve code changes or complex actions, the AI will first create a step-by-step plan. It will post this plan as a comment and wait for the user to approve it by replying with `@gemini-cli plan#123 approved`. This ensures the user has full control before any changes are made.
118+
3. **Execute**: Once the plan is approved (or if no plan was needed), it runs the Gemini model, providing it with the user's request, repository context, and a set of tools.
119+
4. **Commit (if needed)**: If the AI uses tools to modify files, it will automatically commit and push the changes to the branch.
120+
5. **Respond**: The AI posts a final, comprehensive response as a comment on the issue or pull request.
110121

111122
## Configuration
112123

0 commit comments

Comments
 (0)