Skip to content
Agent Orchestration
Command

/ship-ci-review-loop

<ci-review-loop>

BOOST
From plugin
awesome-claude-plugins
1.9k26 skills12 agents26 commands
Install
> /plugin marketplace add composio-community/awesome-claude-plugins

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/ship-ci-review-loop

Context preview

What this command does when you run it.

<ci-review-loop>

Command definition

ship-ci-review-loop.md

<ci-review-loop>

Phase 4: CI & Review Monitor Loop - Reference

This file contains detailed implementation for the CI & Review Monitor Loop phase of `/ship`.

**Parent document**: `ship.md`

---

<mandatory-requirements>

This Phase Is Mandatory

This is not optional. You must: 1. Wait the full 3 minutes for auto-reviewers 2. Run the monitor loop (not just check once) 3. Address all comments before merge </mandatory-requirements>

---

<pr-auto-review>

PR Auto-Review Process

PRs receive automatic reviews from configured auto-reviewers (Copilot, Gemini, CodeRabbit, etc.).

**Mandatory workflow:** 1. After PR creation, wait **at least 3 minutes** for first review round 2. Read **all comments** from all reviewers 3. Address **every comment** - no exceptions 4. Iterate until **zero unresolved threads** (typically 2-4 rounds)

**Rules:**

  • Always address all comments, including "minor" or "nit" suggestions
  • Do not skip a comment unless factually wrong or user-approved
  • Treat all feedback as **required changes**, not suggestions

</pr-auto-review>

---

<overview>

Overview

The monitor loop must wait for: 1. CI to pass 2. All comments resolved (addressed or replied to) 3. No "changes requested" reviews remain

Why All Comments Matter

**Every comment must be addressed:**

  • Critical/High issues: Fix immediately
  • Medium issues: Fix (don't defer)
  • Minor/Nit issues: Fix (shows attention to quality)
  • Style suggestions: Fix (maintains codebase consistency)
  • Questions: Answer with explanation
  • False positives: Reply explaining why, then resolve
  • Not relevant: Reply explaining why, then resolve

Do not ignore comments. Do not leave comments unresolved. A clean PR has zero unresolved conversations. </overview>

The Monitor Loop Algorithm

> **Note:** The JavaScript below is **conceptual pseudocode** showing the algorithm flow. > Implement using bash functions defined in this file.

const MAX_ITERATIONS = 10;  // Safety limit
const INITIAL_WAIT_MS = 180000;  // 3 minutes - wait for auto-reviews
const ITERATION_WAIT_MS = 30000;  // 30 seconds between iterations
let iteration = 0;

while (iteration < MAX_ITERATIONS) {
  iteration++;
  console.log(`\n## CI & Review Monitor - Iteration ${iteration}`);

  // Step 1: Wait for CI to complete
  const ciStatus = await waitForCI();
  if (ciStatus === 'failed') {
    await fixCIFailures();
    continue;  // Push fix, re-run CI
  }

  // Step 1.5: First iteration only - wait for auto-reviews
  if (iteration === 1) {
    console.log("Waiting 3 minutes for auto-reviews...");
    await sleep(INITIAL_WAIT_MS);
  }

  // Step 2: Check for PR comments and reviews
  const feedback = await checkPRFeedback();

  if (feedback.unresolvedCount === 0 && !feedback.changesRequested) {
    console.log("[OK] CI passed, all comments resolved");
    break;  // Ready to merge!
  }

  // Step 3: Address ALL feedback
  await addressAllFeedback(PR_NUMBER);

  // Step 4: Push fixes
  if (feedback.hasCodeChanges) {
    await commitAndPush(`fix: address review feedback (iteration ${iteration})`);
  }

  // Step 5: Sleep before next check
  await sleep(ITERATION_WAIT_MS);
}

Step 1: Wait for CI

wait_for_ci() {
  echo "Waiting for CI checks..."

  while true; do
    CHECKS=$(gh pr checks $PR_NUMBER --json name,state 2>/dev/null || echo "[]")

    PENDING=$(echo "$CHECKS" | jq '[.[] | select(.state | IN("PENDING", "QUEUED", "IN_PROGRESS"))] | length')
    FAILED=$(echo "$CHECKS" | jq '[.[] | select(.state | IN("FAILURE", "CANCELLED"))] | length')
    PASSED=$(echo "$CHECKS" | jq '[.[] | select(.state=="SUCCESS")] | length')

    if [ "$FAILED" -gt 0 ]; then
      echo "[ERROR] CI failed ($FAILED checks)"
      gh pr checks $PR_NUMBER
      return 1
    elif [ "$PENDING" -eq 0 ] && [ "$PASSED" -gt 0 ]; then
      echo "[OK] CI passed ($PASSED checks)"
      return 0
    elif [ "$PENDING" -eq 0 ] && [ "$PASSED" -eq 0 ]; then
      echo "[WARN] No CI checks found, proceeding..."
      return 0
    fi

    echo "  Waiting... ($PENDING pending, $PASSED passed)"
    sleep 15
  done
}

Step 2: Check PR Feedback

check_pr_feedback() {
  local pr_number=$1

  echo "Checking PR feedback..."

  # Extract owner and repo from git remote
  REPO_INFO=$(gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"')
  OWNER=$(echo "$REPO_INFO" | cut -d'/' -f1)
  REPO=$(echo "$REPO_INFO" | cut -d'/' -f2)

  # Get review state
  REVIEWS=$(gh pr view $pr_number --json reviews --jq '.reviews')
  CHANGES_REQUESTED=$(echo "$REVIEWS" | jq '[.[] | select(.state=="CHANGES_REQUESTED")] | length')

  # Get unresolved review threads
  # NOTE: Fetches first 100 threads. For PRs with >100 threads, implement pagination.
  UNRESOLVED_THREADS=$(gh api graphql -f query='
    query($owner: String!, $repo: String!, $pr: Int!) {
      repository(owner: $owner, name: $repo) {
        pullRequest(number: $pr) {
          reviewThreads(first: 100) {
            nodes {
              isResolved
            }
          }
        }
      }
    }
  ' -f owner="$OWNER" -f repo="$REPO" -F pr=$pr_number \
    --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length')

  echo "  Unresolved threads: $UNRESOLVED_THREADS"
  echo "  Changes requested: $CHANGES_REQUESTED"

  echo "{\"unresolvedThreads\": $UNRESOLVED_THREADS, \"changesRequested\": $CHANGES_REQUESTED}"
}

Get Full Thread Details

get_unresolved_threads() {
  local pr_number=$1

  REPO_INFO=$(gh repo view --json owner,name --jq '"\(.owner.login)/\(.name)"')
  OWNER=$(echo "$REPO_INFO" | cut -d'/' -f1)
  REPO=$(echo "$REPO_INFO" | cut -d'/' -f2)

  # NOTE: Fetches first 100 threads. For PRs with >100, implement pagination.
  gh api graphql -f query='
    query($owner: String!, $repo: String!, $pr: Int!) {
      repository(owner: $owner, name: $repo) {
        pullRequest(number: $pr) {
          reviewThreads(first: 100) {
            nodes {
Read more
Ships withawesome-claude-plugins

A curated list of Plugins that let you extend Claude Code with custom commands, agents, hooks, and MCP servers through the plugin system.

Get the whole plugin

Other commands on awesome-claude-plugins.