Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 223 additions & 44 deletions greploop/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,123 +1,264 @@
---
name: greploop
description: >
Iteratively improves a PR until Greptile gives it a 5/5 confidence score with zero
unresolved comments. Triggers Greptile review, fixes all actionable comments, pushes,
re-triggers review, and repeats. Use when the user wants to fully optimize a PR against
Greptile's code review standards.
Iteratively improves a PR (GitHub), MR (GitLab), or shelved changelist (Perforce) until Greptile
gives it a 5/5 confidence score with zero unresolved comments. Triggers Greptile review, fixes all
actionable comments, pushes/re-shelves, re-triggers review, and repeats. Use when the user wants to
fully optimize a PR/MR/CL against Greptile's code review standards.
license: MIT
compatibility: Requires git, gh (GitHub CLI) authenticated, and Greptile installed on the repo.
compatibility: Requires git, gh (GitHub CLI) or glab (GitLab CLI) authenticated, and Greptile installed on the repo. For Perforce, requires p4 CLI authenticated.
metadata:
author: greptileai
version: "1.0"
allowed-tools: Bash(gh:*) Bash(git:*)
version: "1.2"
allowed-tools: Bash(gh:*) Bash(glab:*) Bash(git:*) Bash(p4:*)
---

# Greploop

Iteratively fix a PR until Greptile gives a perfect review: 5/5 confidence, zero unresolved comments.
Iteratively fix a PR/MR/CL until Greptile gives a perfect review: 5/5 confidence, zero unresolved comments.

## Inputs

- **PR number** (optional): If not provided, detect the PR for the current branch.
- **PR/MR/CL number** (optional): If not provided, detect the PR/MR for the current branch, or the default pending changelist for p4.

## Instructions

### 1. Identify the PR
### 0. Detect platform

First check for Perforce, then fall back to git remote detection:

```bash
# Check for Perforce environment
if p4 info >/dev/null 2>&1; then
VCS="perforce"
else
REMOTE_URL=$(git remote get-url origin)
if echo "$REMOTE_URL" | grep -qi "gitlab"; then
VCS="gitlab"
else
VCS="github"
fi
fi
```

For self-hosted GitLab instances whose hostname doesn't contain "gitlab", the user can override by passing `--vcs gitlab` as an input. For Perforce, pass `--vcs perforce`.

### 1. Identify the PR/MR/CL

**GitHub:**
```bash
gh pr view --json number,headRefName -q '{number: .number, branch: .headRefName}'
```

Switch to the PR branch if not already on it.
**GitLab:**
```bash
glab mr view --output json | jq '{iid: .iid, branch: .source_branch}'
```

Switch to the PR/MR branch if not already on it.

**Perforce:**
```bash
# List pending changelists for current user/client
p4 changes -s pending -u $P4USER -c $P4CLIENT

# Describe a specific CL
p4 describe -s <CL_NUMBER>
```

Ensure the correct workspace (`p4 client`) is set before proceeding.

Key field differences:
- GitHub: `number`, `headRefName`, `headRefOid`
- GitLab: `iid`, `source_branch`, `sha`
- Perforce: changelist number, `P4CLIENT`, shelved files

### 2. Loop

Repeat the following cycle. **Max 5 iterations** to avoid runaway loops.

#### A. Trigger Greptile review

Push the latest changes (if any):
Push/shelve the latest changes (if any):

**GitHub/GitLab:**
```bash
git push
```

Wait for checks to start after push:
**Perforce:**
```bash
# Re-shelve to update the shelved files for review
p4 shelve -f -c <CL_NUMBER>
```

Wait for checks to start after push/shelve:

```bash
sleep 5
```

Check if Greptile is already running on this PR before posting a new trigger comment:
**GitHub** — check if Greptile is already running before posting a new trigger comment:

```bash
GREPTILE_STATE=$(gh pr checks <PR_NUMBER> --json name,state | jq -r '.[] | select(.name | test("greptile"; "i")) | .state')
```

If Greptile is **not** already running (`PENDING` or `IN_PROGRESS`), request a fresh review by posting a PR comment (Greptile watches for this trigger):
If Greptile is **not** already running (`PENDING` or `IN_PROGRESS`), request a fresh review:

```bash
if [ "$GREPTILE_STATE" != "PENDING" ] && [ "$GREPTILE_STATE" != "IN_PROGRESS" ]; then
gh pr comment <PR_NUMBER> --body "@greptile review"
fi
```

Then poll for the Greptile check to complete:
Then poll for the Greptile check run to complete:

```bash
HEAD_SHA=$(gh pr view <PR_NUMBER> --json headRefOid -q .headRefOid)
for i in $(seq 1 30); do
STATE=$(gh pr checks <PR_NUMBER> --json name,state --jq '.[] | select(.name | test("greptile"; "i")) | .state | ascii_upcase' 2>/dev/null)

if [ "$STATE" = "SUCCESS" ]; then
echo "Greptile passed!"
break
elif [ "$STATE" = "FAILURE" ] || [ "$STATE" = "ERROR" ]; then
echo "Greptile failed ($STATE)"
exit 1
fi

echo "Waiting for Greptile... (Current state: ${STATE:-PENDING}) attempt $i"
sleep 30
done
```

**GitLab** — check if Greptile is already running before posting a trigger comment:

```bash
PIPELINES=$(glab api "projects/:fullpath/merge_requests/<MR_IID>/pipelines")
GREPTILE_RUNNING=$(echo "$PIPELINES" | jq '[.[] | select(.status == "running" or .status == "pending")] | length')
```

If no pipeline is running, post a trigger comment:

```bash
if [ "$GREPTILE_RUNNING" = "0" ]; then
glab mr note <MR_IID> --message "@greptile review"
fi
```

**Perforce** — Perforce does not have native check runs. If Greptile is integrated via a webhook triggered on `p4 shelve`, wait for it to process. Check your Greptile installation's webhook endpoint or dashboard for the review status. Poll by re-fetching the Greptile review comment on the CL until a score appears.

Then poll for the Greptile pipeline job to complete (see [GitLab API reference](references/gitlab-api.md)):

```bash
HEAD_SHA=$(glab mr view <MR_IID> --output json | jq -r '.sha')

# Poll for Greptile check run to complete (check runs, not action runs)
while true; do
GREPTILE_CHECK=$(gh api "repos/{owner}/{repo}/commits/$HEAD_SHA/check-runs" \
--jq '.check_runs[] | select(.name | test("greptile"; "i"))' 2>/dev/null)

if [ -z "$GREPTILE_CHECK" ]; then
echo "Waiting for Greptile check to appear..."
PIPELINES=$(glab api "projects/:fullpath/merge_requests/<MR_IID>/pipelines")
# Find the most recent pipeline for this SHA
PIPELINE_ID=$(echo "$PIPELINES" | jq -r --arg sha "$HEAD_SHA" \
'[.[] | select(.sha == $sha)] | sort_by(.id) | last | .id // empty')

if [ -z "$PIPELINE_ID" ]; then
echo "Waiting for Greptile pipeline to appear..."
sleep 5
continue
fi

STATUS=$(echo "$GREPTILE_CHECK" | jq -r '.status // "completed"')
CONCLUSION=$(echo "$GREPTILE_CHECK" | jq -r '.conclusion // "pending"')

if [ "$STATUS" = "completed" ]; then
if [ "$CONCLUSION" = "success" ]; then
echo "Greptile check passed!"
else
echo "Greptile check completed with: $CONCLUSION"
fi

JOBS=$(glab api "projects/:fullpath/pipelines/$PIPELINE_ID/jobs")
GREPTILE_JOB=$(echo "$JOBS" | jq '.[] | select(.name | test("greptile"; "i"))')

if [ -z "$GREPTILE_JOB" ]; then
echo "Waiting for Greptile job to appear..."
sleep 5
continue
fi

JOB_STATUS=$(echo "$GREPTILE_JOB" | jq -r '.status')

if [ "$JOB_STATUS" = "success" ] || [ "$JOB_STATUS" = "failed" ] || [ "$JOB_STATUS" = "canceled" ]; then
echo "Greptile job completed with: $JOB_STATUS"
break
fi
echo "Waiting for Greptile... (status: $STATUS)"

echo "Waiting for Greptile... (status: $JOB_STATUS)"
sleep 10
done
```

#### B. Fetch Greptile review results

Get the latest review from Greptile:
Greptile may surface its score in two places — check **both** (three for Perforce):

**GitHub:**

**1. PR description (body):**
```bash
gh pr view <PR_NUMBER> --json body -q '.body'
```

**2. PR reviews:**
```bash
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/reviews
```

Look for the most recent review from `greptile-apps[bot]` or `greptile-apps-staging[bot]`.
Look for the most recent entry from `greptile-apps[bot]` or `greptile-apps-staging[bot]`.

**GitLab:**

**1. MR description (body):**
```bash
glab mr view <MR_IID> --output json | jq -r '.description'
```

**2. MR notes (comments):**
```bash
glab api "projects/:fullpath/merge_requests/<MR_IID>/notes"
```

Parse the review body for:
Filter for notes from the Greptile bot user (check the `author.username` field — the exact username may vary per installation; verify on first run).

- **Confidence score**: Greptile includes a score like `3/5` or `5/5` in its review summary.
- **Comment count**: Number of inline review comments.
**Perforce:**

**1. CL description:**
```bash
p4 describe -s <CL_NUMBER>
```
Check the description field for a Greptile-appended score block.

**2. CL comments / review notes:**
```bash
p4 review -c <CL_NUMBER>
```
Look for comments from the Greptile bot user. The exact mechanism depends on your Greptile installation (it may post via a review tool like Swarm or directly via `p4 review`).

For all platforms, parse the text for:
- **Confidence score**: a pattern like `3/5` or `5/5` (or `Confidence: 3/5`).
- **Comment count**: Number of inline review comments noted in the summary.

Use whichever source has the **most recent** score.

Also fetch all unresolved inline comments:

**GitHub:**
```bash
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/comments
```

Filter to comments from Greptile that are on the latest commit.
**GitLab:**
```bash
glab api "projects/:fullpath/merge_requests/<MR_IID>/discussions"
```

Filter to `DiffNote` type discussions (`notes[0].type == "DiffNote"`) from Greptile that are on the latest commit and not yet resolved (`"resolved": false`).

**Perforce:**
```bash
# Fetch inline diff comments from Greptile bot on the shelved CL
p4 review -c <CL_NUMBER>
```

Filter to comments from the Greptile bot user that have not been marked as resolved/addressed.

#### C. Check exit conditions

Expand All @@ -137,7 +278,7 @@ For each unresolved Greptile comment:

#### E. Resolve threads

Fetch unresolved review threads and resolve all that have been addressed (see [GraphQL reference](references/graphql-queries.md)):
**GitHub** — fetch unresolved review threads and resolve all that have been addressed (see [GraphQL reference](references/graphql-queries.md)):

```bash
gh api graphql -f query='
Expand Down Expand Up @@ -169,15 +310,38 @@ mutation {
}'
```

#### F. Commit and push
**GitLab** — fetch unresolved discussions and resolve each one (see [GitLab API reference](references/gitlab-api.md)):

```bash
glab api "projects/:fullpath/merge_requests/<MR_IID>/discussions?per_page=100"
```

Filter for `"resolved": false` discussions. Then resolve each by its `id`:

```bash
glab api --method PUT \
"projects/:fullpath/merge_requests/<MR_IID>/discussions/<DISCUSSION_ID>" \
--field resolved=true
```

Repeat for each unresolved discussion ID. (GitLab has no batch resolution — loop through each one.)

#### F. Commit and push / re-shelve

**GitHub/GitLab:**
```bash
git add -A
git commit -m "address greptile review feedback (greploop iteration N)"
git push
```

Wait for checks to start after push:
**Perforce:**
```bash
# Stage changes back into the CL and re-shelve for the next review round
p4 shelve -f -c <CL_NUMBER>
```

Wait for checks to start after push/shelve:

```bash
sleep 5
Expand All @@ -191,6 +355,7 @@ After exiting the loop, summarize:

| Field | Value |
| ------------------ | ---------- |
| Platform | GitHub / GitLab / Perforce |
| Iterations | N |
| Final confidence | X/5 |
| Comments resolved | N |
Expand All @@ -202,6 +367,7 @@ If the loop exited due to max iterations, list any remaining unresolved comments

```
Greploop complete.
Platform: GitHub
Iterations: 2
Confidence: 5/5
Resolved: 7 comments
Expand All @@ -212,6 +378,7 @@ If not fully resolved:

```
Greploop stopped after 5 iterations.
Platform: GitLab
Confidence: 4/5
Resolved: 12 comments
Remaining: 2
Expand All @@ -220,3 +387,15 @@ Remaining issues:
- src/auth.ts:45 — "Consider rate limiting this endpoint"
- src/db.ts:112 — "Missing index on user_id column"
```

**Perforce example:**

```
Greploop complete.
Platform: Perforce
Changelist: 12345
Iterations: 3
Confidence: 5/5
Resolved: 9 comments
Remaining: 0
```