Receber por email
← Voltar para o catálogo

phase-pr

Phase-agent wrapper that opens the pull request after implementation completes (CTL-449 Initiative 1 Phase 3). Delegates to `/catalyst-dev:create-pr` (which already auto-runs `describe-pr` and transitions Linear to `inReview`), then writes the PR number + URL into the phase signal file so the downstream `phase-monitor-merge` agent can read it without re-querying GitHub. Dispatched as a `claude --b

18estrelas
Atualizado há 3 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add coalesce-labs/catalyst

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/phase-pr/svg)](https://www.skillteca.com.br/skills/phase-pr)

Skills relacionadas

Ver todas de Pesquisa e Web →

Alerta por categoria

Receba novas skills de Pesquisa e Web toda segunda

1 email curto, só com as skills novas de Pesquisa e Web. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

phase-pr

Thin wrapper around /catalyst-dev:create-pr. The canonical skill already handles: commit, push, base-branch detection, PR creation, describe-pr auto-invocation, workflow-context tracking, Linear inReview transition, and the post-PR resolution loop. Phase-pr adds only the phase-agent envelope plus persisting pr.number + pr.url to the signal file for phase-monitor-merge.

Prerequisites

  • CATALYST_ORCHESTRATOR_DIR, CATALYST_ORCHESTRATOR_ID, CATALYST_PHASE=pr, CATALYST_TICKET

set by [[phase-agent-dispatch]].

  • The prior phase's signal file ${ORCH_DIR}/workers/<TICKET>/phase-review.json exists with

status=done — the dispatcher validates this; this skill assumes it.

  • Current working directory is the ticket's worktree on the implementation branch (not main).

Prelude

set -euo pipefail

: "${CATALYST_ORCHESTRATOR_DIR:?required}"
: "${CATALYST_ORCHESTRATOR_ID:?required}"
: "${CATALYST_PHASE:?required}"
: "${CATALYST_TICKET:?required}"

ORCH_DIR="$CATALYST_ORCHESTRATOR_DIR"
ORCH_ID="$CATALYST_ORCHESTRATOR_ID"
PHASE="$CATALYST_PHASE"
TICKET="$CATALYST_TICKET"
CHANNEL="${ORCH_ID}"

SIGNAL_FILE="${ORCH_DIR}/workers/${TICKET}/phase-${PHASE}.json"
[[ -f "$SIGNAL_FILE" ]] || { echo "phase-${PHASE}: signal file missing" >&2; exit 1; }

PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
[[ -n "$PLUGIN_ROOT" ]] || PLUGIN_ROOT="$(dirname "$(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo .)")")")"

COMMS="${PLUGIN_ROOT}/scripts/catalyst-comms"
[[ -x "$COMMS" ]] || COMMS="$(command -v catalyst-comms 2>/dev/null || true)"
if [[ -n "$COMMS" && -x "$COMMS" ]]; then
  "$COMMS" join "$CHANNEL" --as "$TICKET" \
    --capabilities "phase-pr: ${TICKET}" \
    --orch "$ORCH_ID" --parent orchestrator --ttl 3600 >/dev/null 2>&1 || true
  "$COMMS" send "$CHANNEL" "phase-pr started" --as "$TICKET" --type info \
    --orch "$ORCH_ID" >/dev/null 2>&1 || true
fi

SESSION_SCRIPT="${PLUGIN_ROOT}/scripts/catalyst-session.sh"
if [[ -x "$SESSION_SCRIPT" ]]; then
  CATALYST_SESSION_ID=$("$SESSION_SCRIPT" start \
    --skill "phase-pr" --ticket "$TICKET" \
    --workflow "${CATALYST_SESSION_ID:-}")
  export CATALYST_SESSION_ID
fi

TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
TMP="${SIGNAL_FILE}.tmp.$$"
# CTL-496: persist catalystSessionId so orchestrate-roll-usage --phase can
# attribute cost to the right session_metrics row.
jq --arg ts "$TS" --arg sid "${CATALYST_SESSION_ID:-}" '
  .status = "running"
  | .updatedAt = $ts
  | if $sid != "" then .catalystSessionId = $sid else . end
' "$SIGNAL_FILE" > "$TMP" \
  && mv "$TMP" "$SIGNAL_FILE"

Already-merged detection (CTL-714)

Before delegating to create-pr, detect whether this branch's HEAD is already contained in origin/main (manual rescue, or a sibling PR landed the same commits). If so, skip PR creation to avoid a duplicate / empty-diff PR. Two complementary checks: git merge-base --is-ancestor (works even if the branch was deleted from the remote) and gh pr list --state merged (recovers the merged PR number for the downstream probe).

The detection fence is side-effect-free so the e2e test can source it in isolation.

git fetch origin main --quiet 2>/dev/null || true
ALREADY_MERGED=0
MERGED_PR_NUMBER=""
MERGED_PR_URL=""

# Check 1: is HEAD already contained in origin/main? (must be in `if` — set -e)
if git merge-base --is-ancestor HEAD origin/main 2>/dev/null; then
  ALREADY_MERGED=1
fi

# Check 2: does a MERGED PR exist for this branch? (--state merged is required —
# `gh pr list --head` with no --state returns only OPEN PRs; orchestrate-verify.sh:563)
BRANCH_NAME="$(git branch --show-current 2>/dev/null || true)"
if [[ -n "$BRANCH_NAME" ]]; then
  MERGED_PR_JSON="$(gh pr list --head "$BRANCH_NAME" --state merged \
    --json number,url --limit 1 2>/dev/null || echo '[]')"
  MERGED_PR_NUMBER="$(echo "$MERGED_PR_JSON" | jq -r '.[0].number // empty' 2>/dev/null || true)"
  MERGED_PR_URL="$(echo "$MERGED_PR_JSON" | jq -r '.[0].url // empty' 2>/dev/null || true)"
  if [[ -n "$MERGED_PR_NUMBER" ]]; then
    ALREADY_MERGED=1
  fi
fi

When ALREADY_MERGED=1, write the disposition into the signal file and complete without creating a PR:

if [[ "$ALREADY_MERGED" -eq 1 ]]; then
  echo "phase-pr: HEAD already in origin/main — skipping PR creation (CTL-714)" >&2
  TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  TMP="${SIGNAL_FILE}.tmp.$$"
  jq --arg ts "$TS" \
     --arg reason "already-merged-to-main" \
     --argjson prNum "${MERGED_PR_NUMBER:-null}" \
     --arg prUrl "${MERGED_PR_URL:-}" '
    .updatedAt = $ts
    | .attentionReason = $reason
    | if $prNum != null then .pr = {number: $prNum, url: $prUrl} else . end
  ' "$SIGNAL_FILE" > "$TMP" && mv "$TMP" "$SIGNAL_FILE"

  "${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
    --phase "$PHASE" --ticket "$TICKET" --status complete
  [[ -n "$COMMS" && -x "$COMMS" ]] && "$COMMS" done "$CHANNEL" --as "$TICKET" >/dev/null 2>&1 || true
  exit 0
fi

Existing open PR detection (CTL-709)

CTL-709's phase-implement may have already opened a draft PR for this branch. Detect it here so we can promote it (gh pr ready) rather than re-entering create-pr's interactive "PR already exists" prompt (create-pr/SKILL.md:96–104) — that prompt would hang a --bg worker forever. Detection order: merged → existing-open → create-new. The detection fence is side-effect-free so the e2e test can source it in isolation.

# CTL-709: phase-implement may have already opened a (draft) PR for this branch.
# Detect it here so we can promote it rather than re-entering create-pr's
# interactive "PR already exists" prompt (create-pr/SKILL.md:96 — would hang --bg).
EXISTING_PR_NUMBER=""
EXISTING_PR_URL=""
EXISTING_PR_IS_DRAFT=""
EXISTING_PR_JSON="$(gh pr view --json number,url,state,isDraft 2>/dev/null || true)"
if [[ -n "$EXISTING_PR_JSON" ]]; then
  if [[ "$(printf '%s' "$EXISTING_PR_JSON" | jq -r '.state // empty' 2>/dev/null)" == "OPEN" ]]; then
    EXISTING_PR_NUMBER="$(printf '%s' "$EXISTING_PR_JSON" | jq -r '.number // empty' 2>/dev/null || true)"
    EXISTING_PR_URL="$(printf '%s' "$EXISTING_PR_JSON" | jq -r '.url // empty' 2>/dev/null || true)"
    EXISTING_PR_IS_DRAFT="$(printf '%s' "$EXISTING_PR_JSON" | jq -r '.isDraft // false' 2>/dev/null || true)"
  fi
fi

When an existing open PR is found, promote it (if draft) and finish — without delegating to create-pr. The promote-and-finish block is NOT side-effect-free.

# CTL-864: cross-host fence — bow out if a takeover superseded us. No-op single-host.
"${PLUGIN_ROOT}/scripts/lib/cluster-fence-guard.sh" --phase "$PHASE" --ticket "$TICKET" || exit 10
if [[ -n "$EXISTING_PR_NUMBER" ]]; then
  echo "phase-pr: promoting existing PR #${EXISTING_PR_NUMBER} (draft=${EXISTING_PR_IS_DRAFT})" >&2
  if [[ -r "${PLUGIN_ROOT}/scripts/lib/draft-pr.sh" ]]; then
    # shellcheck source=/dev/null
    source "${PLUGIN_ROOT}/scripts/lib/draft-pr.sh"
  fi
  # CTL-1051: prove the remote branch (and the PR head) equal the worktree HEAD
  # BEFORE announcing the promoted PR — remediation/rebase may have advanced
  # local HEAD past the draft's pushed commit. Fail-closed: a stale ref is a
  # phase FAILURE, not a silent complete.
  # CTL-1119: rc=3 from draft_pr_push_verify means the push was rejected for
  # missing 'workflow' OAuth scope; escalate with an actionable human_question.
  VERIFIED_SHA=""
  PUSH_VERIFY_RC=0
  # CTL-1119 remediate: capture stdout ONLY (the verified SHA). draft_pr_push_verify
  # writes diagnostic _draft_pr_warn lines to stderr on every retry path (force-with-lease
  # AND the token-routed push); folding them in with 2>&1 made VERIFIED_SHA multi-line, so
  # the PR_HEAD_OID != VERIFIED_SHA guard below always tripped and falsely failed the phase
  # with stale_ref_push_verify_failed. No redirect: stderr flows to the worker log.
  VERIFIED_SHA="$(draft_pr_push_verify)" || PUSH
Compartilhar X LinkedIn