본문으로 건너뛰기
cccskills
무료GitHub에 공개

approve-merge

Record per-PR CEO approval and merge in one turn. ONLY on an explicit per-PR "approved" — never on umbrella "go".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard스타 538(GitHub 538 + cccskills 0)
연동 도구GitHub

실행 점수 미검증

설치 방법 보기

포함된 파일(4)

  • SKILL.md13.5 KB
  • remove-worktree.sh6.8 KB
  • scripts/approve-merge.sh5.7 KB
  • tests/test_merge_invocation_not_substituted.sh10.1 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Writing: read docs/skills/shared/writing-rule.md before you write a durable artifact.

/approve-merge — Record CEO Approval and Merge

Write per-PR approval, then merge through the tracker adapter. Rationale: read docs/skills/approve-merge-rationale.md for gate history and AgDR references.

The one rule you must not break

Invoke only on explicit user approval for this PR. A plan-level "go" is not merge approval. Stop and ask when the PR or intent is unclear. Only the human can invoke this skill (#1042).

Menu path

The operator can also approve from one AskUserQuestion menu. Ask exactly Approve and merge <owner>/<repo>#<N> at <40-hex-sha>? with options Approve and merge and Not yet only. A hook writes the record; never write it yourself. See AgDR-0234.

Process

0. Resolve the configured approver display title (SOFT, prose-only)

Before addressing the user in any confirmation question or report, read the configured display title for the human per-PR merge approver:

source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
APPROVER_TITLE=$(config_get_or '.review_markers.human_approver_title' 'CEO')

Use $APPROVER_TITLE in prose only. Keep the -ceo filename and marker fields unchanged. If config or jq is unavailable, use CEO. This display setting must not block the skill.

1. Parse the PR number, the repo, and flags

Extract the PR number from $ARGUMENTS. If no number is given, try to infer from:

  • The current branch's open PR via gh pr view --json number --jq '.number'
  • The user's most recent message, if it named a PR explicitly

If the PR number is ambiguous (multiple PRs on the branch, unclear which was approved), STOP and ask the user which PR.

Resolve REPO from owner/repo#N or an explicit owner/repo argument. Otherwise, parse the current checkout's git remote get-url origin. Do not use ambient gh resolution or headRepository (#887). Scope each host query to REPO, then PR_HOST_REPO after step 4. Recognise --no-merge. It writes approval without merging.

2. Sanity-check the user's intent

Re-read the latest message and apply the one rule above. Treat a reply to your "Ready to merge PR #X — approved?" question as naming this PR. Stop on an umbrella "go" and ask:

"PR #X is ready to merge. Just confirming — explicit approval to merge PR #X, now?" Proceed only after an unambiguous per-PR approval.

3. Resolve the ops root

Keep this bootstrap inline. It locates the script from workspace clones and split-portfolio forks (#229, #230, #559).

OPS_ROOT=""
PIN_FILE="${APEXYARD_OPS_PIN_DIR:-$HOME/.claude/apexyard}/ops-root-${CLAUDE_CODE_SESSION_ID:-}"
if [ -z "${APEXYARD_OPS_DISABLE_PIN:-}" ] && [ -n "${CLAUDE_CODE_SESSION_ID:-}" ] && [ -f "$PIN_FILE" ]; then
  IFS= read -r OPS_ROOT < "$PIN_FILE" || OPS_ROOT=""
fi
# Validate the pin (self-heal a stale one): must satisfy a fork anchor.
if [ -n "$OPS_ROOT" ] && [ ! -f "$OPS_ROOT/.apexyard-fork" ] && \
   { [ ! -f "$OPS_ROOT/onboarding.yaml" ] || [ ! -f "$OPS_ROOT/apexyard.projects.yaml" ]; }; then
  OPS_ROOT=""
fi
# Fallback: walk up from git toplevel (pre-#381 behaviour, safety net).
if [ -z "$OPS_ROOT" ]; then
  r=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
  while [ -n "$r" ] && [ "$r" != "/" ]; do
    if [ -f "$r/.apexyard-fork" ] || { [ -f "$r/onboarding.yaml" ] && [ -f "$r/apexyard.projects.yaml" ]; }; then
      OPS_ROOT="$r"; break
    fi
    r=$(dirname "$r")
  done
fi
MARKER_HOME="${OPS_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"

4. Check merge prerequisites once

Run this command once. Capture stdout. Show stderr to the user.

CHECK_JSON=$(bash "$MARKER_HOME/.claude/skills/approve-merge/scripts/approve-merge.sh" <pr> "$REPO" "$MARKER_HOME")
CHECK_RC=$?
[ "$CHECK_RC" -eq 0 ] || exit "$CHECK_RC"
printf '%s' "$CHECK_JSON" | jq -e '
  type == "object" and
  (.marker_home | type == "string" and length > 0) and
  (.pr_host_repo | type == "string" and length > 0) and
  (.strategy | type == "string" and length > 0) and
  (.head_sha | type == "string" and length > 0) and
  (.release_class | type == "boolean")' >/dev/null || exit 1
MARKER_HOME=$(printf '%s' "$CHECK_JSON" | jq -r '.marker_home') || exit 1
PR_HOST_REPO=$(printf '%s' "$CHECK_JSON" | jq -r '.pr_host_repo') || exit 1
MERGE_STRATEGY=$(printf '%s' "$CHECK_JSON" | jq -r '.strategy') || exit 1
RELEASE_CLASS=$(printf '%s' "$CHECK_JSON" | jq -r '.release_class') || exit 1
case "$MERGE_STRATEGY" in
  squash|merge|rebase) ;;
  *) echo "Invalid merge strategy." >&2; exit 1 ;;
esac
source "$MARKER_HOME/.claude/hooks/_lib-review-markers.sh" || exit 1
CHECK_SHA=$(printf '%s' "$CHECK_JSON" | jq -r '.head_sha') || exit 1
HEAD_SHA=$(gh pr view <pr> --repo "$PR_HOST_REPO" --json headRefOid -q '.headRefOid') || exit 1
[ -n "$HEAD_SHA" ] && [ "$HEAD_SHA" = "$CHECK_SHA" ] || { echo "PR head changed after the check." >&2; exit 1; }

Stop on any non-zero exit, invalid JSON, or missing field. NEVER assume a pass. Accept only squash, merge, rebase as strategy values.

  • Exit 10: the branch is behind and the base touches shared files. Tell the user to update the branch. Show gh pr update-branch <pr> --repo "$REPO". Wait for green CI. Run the carry-over check before requesting a Rex delta review. Use AskUserQuestion with "Run it yourself (Recommended)" and "Approve me to run it". The first choice means the operator runs the command. The second permits the agent to push the branch update. Update nothing before the operator chooses. If the harness lacks this tool, show the same choices as a numbered list.
  • Exit 11: the behind check could not run. Retry or verify the base manually. Stop before writing any marker.
  • Exit 12: the Rex marker is missing or stale. Never move, rename, or copy a marker file. Use rm to delete the gate-invisible file named in stderr. Re-run /code-review <pr> without a marker path.
  • Exit 13: the release PR title or body is unreadable. Fix the read failure and retry. Never use a bare squash fallback.
  • Other exit: show the error and stop.

5. Write the structured CEO marker

The marker is a key/value file with required fields. The format:

sha=<40-char hex — must be the PR HEAD from step 3>
approved_by=user
approved_at=<ISO-8601 UTC timestamp, e.g. 2026-05-03T13:25:42Z>
skill_version=2
approval_summary="<truncated user approval message, ≤200 chars>"

Keep all marker fields below. Use the ops root and the repo-qualified review_marker_path helper (AgDR-0060, #765). HEAD_SHA is the script's head_sha, confirmed by a fresh read in step 4.

# (MARKER_HOME and PR_HOST_REPO already resolved in step 4 — reuse them here.)
mkdir -p "$MARKER_HOME/.claude/session/reviews"
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Sanitise: drop newlines, drop shell-special chars, truncate to 200.
summary=$(echo "<user approval message>" | tr '\n' ' ' | tr -d '"`$\\' | cut -c1-200)

# CEO marker keyed on the BASE repo — same key as Rex's marker and the gate's
# lookup (#765). Keying it on the fork would leave a cross-fork merge blocked.
CEO=$(review_marker_path "$PR_HOST_REPO" <pr> ceo "$MARKER_HOME")
cat > "$CEO" <<EOF
sha=${HEAD_SHA}
approved_by=user
approved_at=${ts}
skill_version=2
approval_summary="${summary}"
EOF

6. Merge with the checked strategy

Skip this step for --no-merge. Run this entire block in one shell. Shell variables from step 4 do not carry over. The block re-runs the script and stops on a missing or invalid strategy or release_class (#1196). Fill in <marker_home>, <repo> and <pr> with literal values. Pass those same <repo> and <pr> literals to tracker_pr_merge. The merge gate reads the command text before the shell expands variables (#1758). The block stops when the checked host repo is not <repo>. The tracker_pr_merge call MUST be invoked as a bare top-level statement, never inside $(...) or backticks. The merge-gate matcher reads the raw command text and may not fire on a wrapped call. Redirect stdout to a file, then read the result in a separate step.

MARKER_HOME="<marker_home>"
CHECK_JSON=$(bash "$MARKER_HOME/.claude/skills/approve-merge/scripts/approve-merge.sh" <pr> "<repo>" "$MARKER_HOME") || exit 1
PR_HOST_REPO=$(printf '%s' "$CHECK_JSON" | jq -r '.pr_host_repo | tostring') || exit 1
MERGE_STRATEGY=$(printf '%s' "$CHECK_JSON" | jq -r '.strategy | tostring') || exit 1
RELEASE_CLASS=$(printf '%s' "$CHECK_JSON" | jq -r '.release_class | tostring') || exit 1
[ -n "$PR_HOST_REPO" ] && [ "$PR_HOST_REPO" != null ] || exit 1
case "$MERGE_STRATEGY" in squash|merge|rebase) ;; *) echo "Invalid strategy." >&2; exit 1 ;; esac
case "$RELEASE_CLASS" in true|false) ;; *) echo "Invalid release_class." >&2; exit 1 ;; esac
[ "$PR_HOST_REPO" = "<repo>" ] || { echo "Checked host repo does not match <repo>." >&2; exit 1; }
source "$MARKER_HOME/.claude/hooks/_lib-tracker.sh" || exit 1
RELEASE_SUBJECT=""
RELEASE_BODY_FILE=""
if [ "$RELEASE_CLASS" = true ]; then
  RELEASE_SUBJECT=$(gh pr view <pr> --repo "$PR_HOST_REPO" --json title -q '.title') || exit 1
  if [ -z "$RELEASE_SUBJECT" ]; then
    echo "ERROR: could not read the release PR's title." >&2
    exit 1
  fi
  RELEASE_BODY_FILE=$(mktemp) || exit 1
  if ! gh pr view <pr> --repo "$PR_HOST_REPO" --json body -q '.body' > "$RELEASE_BODY_FILE" \
     || [ ! -s "$RELEASE_BODY_FILE" ]; then
    echo "ERROR: could not read the release PR's body. Refusing a bare squash." >&2
    rm -f "$RELEASE_BODY_FILE"
    exit 1
  fi
fi
MERGE_RESULT_FILE=$(mktemp) || exit 1
tracker_pr_merge "<repo>" "<pr>" "${MERGE_STRATEGY}" true "$RELEASE_SUBJECT" "$RELEASE_BODY_FILE" > "$MERGE_RESULT_FILE"
MERGE_RC=$?
MERGE_RESULT="$(cat "$MERGE_RESULT_FILE")"
MERGE_SHA=$(printf '%s' "$MERGE_RESULT" | jq -r '.sha // empty' 2>/dev/null)
rm -f "$MERGE_RESULT_FILE" "$RELEASE_BODY_FILE"

7. Process the merge result

On MERGE_RC 0, report MERGE_SHA and continue. On any other result, show the exact error. The CEO marker stays on disk for a retry. MERGE_RC 3 means tracker.kind is none. Report approval and ask the user to merge manually on the host. No further /approve-merge invocation is needed after that manual merge.

8. Move the board card to "Measurement" (opt-in)

After a successful merge, call board_move_card to signal that the work has shipped and is entering the measurement/observe phase. This is a no-op unless enable_auto_moves is true in the fork's github_projects config.

source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-project-board.sh"
board_move_card "<pr>" "measurement"

board_move_card degrades gracefully — any failure warns to stderr and returns 0. It never blocks the merge report.

9. Report

Single-line confirmation (include the merge strategy used so the operator can see it):

✓ Merged PR #<pr> as commit <MERGE_SHA> (strategy: squash). Branch deleted.

or for sync PRs:

✓ Merged PR #<pr> as commit <MERGE_SHA> (strategy: merge, auto-detected sync PR — ancestry preserved). Branch deleted.

After a successful merge (MERGE_RC 0), add this final report line:

Next: run /compact to free context before the next task.

If the merge gate blocked (MERGE_RC non-zero and not 3), surface the exact error and tell the user how to retry:

✗ Merge blocked: <reason from gate>. Marker still on disk at <CEO path from review_marker_path> — fix the issue and re-invoke `/approve-merge <pr>` (the marker is still valid, no need to re-approve).

If MERGE_RC = 3 (tracker.kind: none, no host CLI configured):

✓ CEO approval recorded for PR #<pr>. No tracker CLI is configured (tracker.kind: none) — merge <pr> manually on the host; the marker on disk covers the approval, no further /approve-merge invocation needed.

When the merged PR adds a slice record, print Next: /orbit handoff --slice <path> to file the proposed slice.

9a. Optional: offer to remove the merged PR's worktree

Skip unless MERGE_RC is 0 without --no-merge. Follow docs/skills/approve-merge-worktree-offer.md. List candidates with .claude/skills/approve-merge/remove-worktree.sh list "<source-tree>" "<head-branch>". Ask with AskUserQuestion ("Remove it" or "Keep it") and show the ignored files the helper lists. On "Remove it", run remove-worktree.sh remove "<source-tree>" "<path>" "<head-branch>". Never remove a path the helper did not list. Never pass --force. Never remove without a yes.

10. Optional: post-merge child-issue closure

Do this only when the user explicitly asks. Do not auto-close a child issue. Confirm each close on its own. Full rules: docs/skills/approve-merge-rationale.md.

--no-merge opt-out

/approve-merge <pr> --no-merge writes the marker and stops. Useful when:

  • CI is still running and you want to record approval first
  • You want to defer the merge for a batch
  • A teammate will do the merge
  • A regulated environment separates approval from execution

The skill never writes the marker AND defers the merge in any other case. The default IS auto-merge.

Notes

  • The marker is gitignored session state.
  • A retry replaces the marker with the current head and timestamp.
  • New commits invalidate approval unless the gate verifies Rex carry-over (#1437, #1456). Never rewrite a Rex marker.
  • Legacy bare-SHA markers are rejected. Run this skill again to write version 2.
  • Invoke this skill on human approval. Do not poll for approval.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

WCAG 2.1 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

WCAG 2.2 AA audit — perceivable, operable, understandable, robust criteria. Deep-dive for /launch-check accessibility.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

agdr

무료

Browse / search / show / stats AgDRs across the portfolio — recalls "have we decided this before?".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

Analytics audit — SDK config, event naming, funnel completeness, dashboards. Deep-dive for /launch-check analytics.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

Record per-PR architecture-review approval for design-artifact PRs (required by the architecture gate). ONLY on an explicit per-PR architect "approved".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

Record per-PR design-review approval (UI merge gate). ONLY on an explicit per-PR designer "approved".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

me2resh/apexyard5382026년 10월 26일 업데이트

me2resh의 스킬 모두 보기

이 스킬의 문제 신고