PR 열기
PR을 잘 여는 것은 git 문제라기보다 연구 문제입니다. 리뷰어는 첫 화면에서 작성자가 리포지토리를 이해하는지 판단합니다. — 제목 문법, 본문 형태, 커밋 제목 모두가 이 신호를 보내므로, 해야 할 일은 이 리포지토리 관리자가 작성하는 방식을 배우고 그대로 쓰는 것입니다.
먼저 깊이 선택
익스프레스는 "그냥 PR 열어", "그냥 제출해", "이거 넣자"와 같은 경우를 위한 것입니다. — 연구 비용이 절약하는 리뷰 비용보다 더 클 정도로 충분히 작은 변경에도 사용합니다. 방향을 잡고, 작성하고, 제출하고, 모니터링을 넘깁니다. 스타일 스카우트, 스타일 카드, 음성 교정, 산문 검증(gate)을 건너뜁니다. 익스프레스가 원하는지 묻지 마세요: 이미 문구에서 말하고 있으므로, 확인하는 것은 사용자가 피하려고 했던 지연입니다.
ccx vcs lane --json
ccx vcs diff
ccx vcs ship --no-watch --pr-title-file "$TITLE" --pr-body-file "$BODY"
본문에는 여전히 변경 사항과 그 이유가 포함되며, 리포지토리에 템플릿이 있으면 그 템플릿을 따르고, 제목은 최근 병합된 몇 개 PR의 문법과 일치합니다. — gh pr list --state merged --limit 5 --json title 한 번 읽는 것으로, 스카우트는 아닙니다. 익스프레스는 연구를 생략하지만 본문은 생략하지 않습니다: 본문 없는 PR은 어떤 레인도 제출하지 않습니다.
풀은 이 파일의 나머지 부분에 해당하며, 사용자가 소유하지 않은 리포지토리에서, 관리자를 설득해야 하는 변경에서, 또는 어딘가에 처음으로 PR을 열 때 그 비용을 충당합니다. 외부 리포지토리에서는 준수 및 예의 검사가 익스프레스에서도 선택 사항이 아닙니다. — 건너뛴 손님은 자신의 시간이 아닌 관리자의 왕복 시간을 낭비합니다.
방향 잡기
여기서 경로가 중요합니다: 세 가지 명령어의 이름이 지정된 출력은 이후 모든 단계에서 참조됩니다.
ccx vcs lane --json # lane, branch, trunk, dirty, github.mine, 다운스택 PR 및 본문 없는 PR
gh repo view --json isPrivate,owner,viewerPermission,pullRequestTemplates,nameWithOwner,defaultBranchRef
ccx vcs guidelines # CONTRIBUTING, PR 템플릿, 행동 강령, 이슈 설정 — 카드 경로 출력
그런 다음 변경 사항 자체에 대해 ccx vcs diff를 실행합니다.
ccx vcs lane은 레인을 보고합니다. — 선택하는 것이 아니며, 사용자도 선택하지 않습니다. ccx vcs ship은 제출 시점에 jj, git, 또는 gt를 선택하며, 사용자가 제어하지 않는 리포지토리는 자동으로 gt 레인에서 제외되므로, ship이 레인을 보고하기 전에 레인을 가정하는 내용을 작성하지 마세요. github.mine은 소유권 판정입니다. 그 뒤의 테스트와 mine: false 이후의 모든 것은 reference/oss.md에 있습니다. ccx vcs guidelines가 작성한 카드를 읽은 다음, 하드 요구사항과 선호사항을 분리하세요. — reference/guidelines.md에 그 차이점이 있고, 아무것도 게시하지 않는 리포지토리에 대한 gh api 대체 방법이 있습니다.
하우스 스타일 학습
하우스 스타일은 디스크의 카드에 저장되며, 리포지토리당 한 번 스카우트되고 캐시됩니다:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/pr-cache.sh" status <owner/name> # fresh | stale:<reason> | missing
bash "${CLAUDE_PLUGIN_ROOT}/scripts/pr-cache.sh" path <owner/name> # 캐시 디렉토리 출력
fresh는 스카우트가 필요 없습니다. — 카드를 읽고 진행하세요. stale 또는 missing → 스카우트를 한 번 생성합니다(실행당 리포지토리당 한 번):
Agent(subagent_type: "open-pr:pr-style-scout",
prompt: "repo: <owner/name>\ncache: <dir>\nviewer: <gh login>")
스카우트는 최근 병합된 PR을 샘플링하고, 봇, AI가 작성한 PR, 그리고 외부 리포지토리에서는 뷰어 자신의 PR을 필터링한 후 캐시 디렉토리에 카드를 작성합니다. 경로, 짧은 요약, 신뢰도를 반환합니다. — 작성된 카드를 읽고, 내레이션을 읽지 마세요.
카드는 샘플 크기와 축별 신뢰도를 포함합니다. 사람 샘플을 유지하는 필터가 샘플을 줄이기 때문입니다. 제목 문법과 본문 구조는 관례입니다. — 어떤 샘플 크기에서도 유지됩니다. 리뷰 응답 어조와 산문 레지스터는 더 적은 신호에 의존합니다: confidence: low에서는 규칙이 아닌 가이드라인이며, PR 템플릿과 가이드라인이 더 많은 비중을 차지합니다. 세 개의 샘플에서 읽은 관례는 그 세 개를 설명합니다. 캐시 레이아웃, 카드 스키마, 그리고 만료 기준은 reference/style-cache.md에 있습니다.
음성 교정
사용자에게 로컬 wlm 프로필이 있는 경우, 형식 변경에도 살아남는 사용자 음성의 일부를 차용합니다:
wlm profile list
wlm -p <profile> stylecard show # -p는 서브커맨드 앞에 와야 하며 생략 불가
wlm 카드는 긴 형식의 블로그 산문을 위해 구축되었습니다: 절대 사용하지 않는 목록(Never list)과 단어 선택은 그대로 옮기고, 어조는 절반 강도로, 길이와 시작 표현은 전혀 옮기지 않습니다. 전체 전환 테이블과 프로필이 없는 경우의 대체 방법은 reference/voice.md에 있습니다. 리포지토리의 카드와 음성 카드가 일치하지 않는 경우, 리포지토리가 우선합니다. — 당신은 그들의 형식에 온 손님입니다.
사용자가 제어하지 않는 리포지토리
github.mine: false는 레지스터를 변경하고 글을 쓰기 전에 두 가지 검사를 추가합니다(reference/oss.md 참조):
- 예의 레지스터 — 변경 사항에 대해 직접적으로 말하고, 결정에 대해서는 겸손하게 말합니다: 숫자가 주장을 뒷받침하고, 관리자는 자신의 결정을 유지합니다.
- 표준 준수 —
ccx vcs diff를 가이드라인 카드 및 체크리스트와 비교: 범위, 스타일, 테스트, 문서, 커밋 위생, 리포지토리의 병합된 PR 표준 대비 diff 크기. 각 누락은 수정하거나, 본문에서 편차를 인정하는 문장을 포함합니다. - 이슈 우선 — 가이드라인 카드가 이슈를 하드 요구사항으로 표시한 경우, 열기 전에 검색:
gh issue list --repo <slug> --search "<keywords>" --state all. 일치하는 항목 없음 → 이슈 초안 작성 후,AskUserQuestion으로 초안을 사용자에게 보여줍니다. — 초안 그대로 열기 / 먼저 수정 / 기존 이슈 연결 / 의도적으로 요구사항 건너뛰기 — 사용자의 선택에 따라 작업합니다. 이슈를 열면 사용자 이름으로 다른 사람의 프로젝트에 게시됩니다. 초안 작성은 그렇지 않습니다.
작성
제목, 본문, 커밋 제목은 명시된 신뢰도로 스타일 카드를 따릅니다. PR 템플릿이 있으면 본문의 뼈대가 됩니다. 형용사로 변경 사항을 평가하는 곳마다 숫자를 사용합니다: 리뷰어는 "콜드 부팅에서 40ms 단축"을 확인하고 "훨씬 빠름"은 무시합니다.
<예시> <예시 레이블="과장"> "이 PR은 설정 로딩을 엄청나게 개선합니다! 이제 훨씬 더 견고합니다." 리뷰어가 확인할 수 없는 형용사로 자신을 평가하며, 형식이 담을 수 없는 기쁨을 표현합니다. </예시> <예시 레이블="정확"> "설정 로딩이 더 이상 키마다 파일을 다시 읽지 않습니다. — 시작 시 한 번 읽고, 콜드 부팅에서 40ms 단축." 변경 사항과 측정된 효과를 명시하고, 리뷰어는 신뢰하는 대신 확인합니다. </예시> </예시>
본문과 제목을 파일에 작성하고, 셸 인수로 전달하지 마세요. — 백틱, $, 또는 코드 펜스가 포함된 본문은 명령줄에서 온전히 남지 않으며, 제목은 그보다 적은 경우에도 거부됩니다(Ship 참조):
BODY=$(mktemp)
TITLE=$(mktemp)
본문은 산문이므로, 제출 전에 산문 검증(gate)을 통과해야 합니다: slop-cop check "$BODY" --lang=markdown, 모든 플래그를 수락하기보다는 분류합니다.
커밋 메시지도 동일한 처리를 받습니다: 카드는 이 리포지토리의 제목 문법, 길이, 자체 병합 커밋이 사용하는 트레일러를 기록합니다. — 해당 트레일러와 일치시키고 다른 것은 사용하지 마세요. 기록에 Co-Authored-By: Claude 또는 Generated with Claude Code가 없는 리포지토리에서 도구 사용을 알리는 트레일러는 신중한 PR이 자동화된 것으로 읽히게 만드는 단일 라인이며, 관리자가 이를 기준으로 필터링합니다. 반면, 기록에 이러한 트레일러가 있는 리포지토리에서는 유지하는 것이 관례입니다.
세션당 하나의 스택
하나의 세션에서 관련 작업은 하나의 체인 스택으로 들어가며, 트렁크에서 독립적인 형제로 나뉘지 않습니다. — diff가 증명 가능하게 분리되어 있더라도 마찬가지입니다. "파일이 겹치지 않는다"는 분할 이유가 아닙니다: 형제는 체인이 명시하는 리뷰 순서를 지우고, 중간 스택 메커니즘을 잃습니다. — 하나의 리스택은 수정 사항을 위의 모든 브랜치에 전달하는 반면, 형제는 각각 혼자 리베이스됩니다. 의존성 순서로 체인을 구성하고, 소비하는 것이 위에 있는 것을 소비합니다. 아직 진행 중인 레인은 팁에 두어, 아래의 완료된 브랜치가 그 변경 아래에서 다시 리베이스되지 않도록 합니다. 메커니즘은 reference/stack.md에 있습니다.
PR은 하나의 기능 크기 작업 단위입니다. 후속 작업, 수정, 리뷰 응답은 열린 PR에 대한 커밋이며, 스택된 자식이 아닙니다. ccx vcs ship으로 제출하고, --amend를 추가하여 커밋에 접거나, 스택의 팁만 변경된 경우 --tip-only를 사용합니다. 부모가 다른 작업을 진행하기 전에 랜딩되어야 하거나, 리뷰가 분할을 필요로 하는 경우에만 스택합니다. 편집할 때마다가 아니라 일관된 체크포인트에서 푸시합니다. 각 푸시는 실행 중인 빌드를 대체하고 CI와 리뷰 봇을 다시 실행하기 때문입니다. 그래도 한 시간 내에 푸시하세요. origin이 백업이기 때문입니다.
제출
플래그 조합이 중요합니다:
ccx vcs ship --no-watch --pr-title-file "$TITLE" --pr-body-file "$BODY"
제목을 본문 파일 옆에 있는 한 줄 파일로 작성하고 --pr-title-file로 전달하며, 인라인 --pr-title는 절대 사용하지 마세요. 작업 트리 격리된 에이전트의 Bash 샌드박스는 따옴표와 단어 git 또는 셸 이름을 쌍으로 사용하는 모든 인수를 거부합니다(예: GitHub's ... git). 파일에 있는 제목은 명령줄에 도달하지 않습니다. Ship은 제목에서 커밋 제목을, PR 본문에서 커밋 본문을 가져오므로, 제목과 다른 주제에만 -m이 필요합니다.
--no-watch는 ship의 내장 감시가 CI가 결론날 때까지 블로킹되기 때문입니다. — 기다릴 때는 맞지만, 감시를 곧 넘겨야 할 때는 틀립니다. Ship은 모든 레인에서 PR 생성 및 업데이트를 소유합니다: 수동 gh pr create는 그 옆에서 gt 레인이 자체 스택과 조정할 수 없는 PR을 생성합니다.
이미 열려 있는 PR에서 ship은 경고하고 현재 제목과 본문을 유지하므로, 수정은 두 플래그를 모두 전달하지 않고, 텍스트를 변경해야 하는 PR은 --replace-pr-text를 사용합니다.
gt 레인에서 한 번의 제출이 여러 PR을 생성할 수 있으며, 제출의 모든 PR 중 아직 본문이 없는 모든 PR에 본문이 제공됩니다. --pr-title-file과 --pr-body-file은 반복되며 브랜치별로 범위가 지정됩니다(--pr-body-file <branch>=<path>; 값이 없는 것은 팁에 적용됨). ccx vcs lane --json은 어떤 다운스택 PR이 본문 없는지 알려줍니다.
거부는 재시도가 아니라 정보입니다: nothing to commit은 이전 ship이 이미 이것을 랜딩했음을 의미합니다. ship은 gt needs_restack을 스스로 재생합니다. 브랜치가 부모에서 벗어나거나 트렁크가 이동하여 ship이 여전히 거부하는 경우, ccx vcs stack submit을 실행하고(또는 다른 레인이 소유할 때 거부 내용을 스택 소유자에게 그대로 전달), 그런 다음 ship을 다시 실행합니다.
Ship은 브랜치, PR 번호와 URL, head sha를 보고합니다. 핸드오프에는 이 네 가지 모두가 필요합니다.
모니터링 위임
${CLAUDE_PLUGIN_ROOT}를 스폰 시점의 호출자에서 절대 경로로 확인하고 poll:에 실행 준비가 된 명령어를 채웁니다. 이 변수는 이 스킬의 텍스트에서 치환되며, 하위 에이전트는 이를 받지 않습니다. poll: 뒤의 문장을 그대로 전달하여 감시자가 하네스가 에이전트 정의를 생략하더라도 스크립트를 장착할 수 있도록 합니다.
Agent(subagent_type: "open-pr:pr-watcher", run_in_background: true,
name: "pr-watch-<number>",
prompt: "pr: <number>\nurl: <url>\nrepo: <owner/name>\nhead: <sha>\n"
"branch: <branch>\nlane: <gt|jj|git>\ncache: <dir>\nownership: <mine|foreign>\n"
"poll: bash \"<abs plugin root>/scripts/pr-poll.sh\" <owner/name> <number> <cache>/pr/<number>.json\n"
"PR_POLL_WINDOW=40 <poll: command>를 단독으로 포어그라운드 Bash로 실행(timeout: 60000), DONE window-elapsed 시 즉시 다시 실행하고 최종 판정이 나올 때까지 계속 호출: Monitor를 장착하거나 호출을 백그라운드로 전환하지 마세요. 어느 쪽도 하위 에이전트를 깨우지 않습니다. 루프로 감싸지 마세요. 수동 gh pr checks/bk 루프로 대체하지 마세요. 큐 랜딩을 볼 수 없습니다. DONE ready-to-merge 시, 다른 작업보다 먼저 SendMessage main을 보내세요.")
스폰 후, 폴 스크립트가 상태 파일을 작성했는지 확인합니다:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/pr-watch-armed.sh" "<cache>/pr/<number>.json"
종료 코드 1인 경우, 동일한 poll: 라인을 감시자에게 다시 SendMessage하고 확인을 한 번 더 실행합니다. 다시 실패하면 사용자에게 감시가 장착되지 않았음을 보고합니다. 에이전트 스폰만으로 스크립트가 실행 중임을 증명하지는 않습니다.
PR당 하나의 감시자 — 동일한 PR에 두 번째 감시자는 첫 번째를 덮어씁니다. evicted를 제외한 모든 보고는 감시를 종료합니다. SendMessage를 이름으로 호출하여 다음 라운드를 위해 다시 시작합니다. evicted: <reason> <detail> 메시지는 실행 중간에 도착하며, 감시자는 호출자가 레이블 또는 Graphite UI를 통해 다시 대기시킬 때까지 계속 감시합니다. 다시 대기시키기 전에 수정 사항을 푸시하세요. 다시 대기 여부는 호출자가 결정합니다.
ready-to-merge 보고 후: open-pr.on-ready
ready-to-merge 보고는 PR이 녹색이고, 병합 가능하며, 승인되었음을 의미합니다: 모든 검사 통과, head의 모든 GitHub Actions 검사 스위트 완료, mergeable이 true, 큐 상태가 깨끗하게 읽힘(QUEUE unknown 아님), PR이 현재 head에서 큐에 있거나 evicted되지 않음. 리뷰어의 최신 리뷰가 CHANGES_REQUESTED가 아니고, REST mergeable_state가 blocked가 아님(필수 승인이 누락된 상태). ready-to-merge downstack #<n> 보고는 다운스택 PR #을 기다리는 스택된 PR입니다(Graphite의 병합 가능성 검사가 레거시 Graphite 스택에서 유일하게 완료되지 않은 검사인 경우 포함). PR 자체는 준비되었지만, 다운스택과 함께만 랜딩됩니다: 질문에 #을 명시하고 스택을 대기시키고, 이 PR만 따로 대기시키지 마세요.
사용자의 CLAUDE.md에서 다음 정확한 라인 중 하나를 읽으세요:
open-pr.on-ready: offer-open(라인이 설정되지 않은 경우 기본값): 동일한 턴에서AskUserQuestion을 통해 PR 페이지를 열지 사용자에게 물어보세요.gh pr view <number> --web을 '예'일 때만 실행하세요.open-pr.on-ready: offer-merge: 동일한 턴에서AskUserQuestion을 통해 "#를 지금 병합할까요?"라고 물어보세요. '예'일 때만 리포지토리의 큐 레이블을 추가하세요.
보고만으로 레이블을 추가하거나 페이지를 열지 마세요. long-running 스킬 하에서는 보고를 랜딩 데스크로 전달하세요. 설정과 관계없이 결정권은 랜딩 데스크에 있습니다.
이미 ready-to-merge를 보고한 감시자가 있는 PR에 큐 레이블을 추가한 후, 이름으로 해당 감시자를 다시 시작하여 녹색 검사를 통해 큐를 감시하고 랜딩 또는 실패까지 확인하세요:
SendMessage(to: "pr-watch-<number>", message: "Queue label <label> added. Resume the queue watch on the same state file.")
그 보고는 작업 중간에 알림으로 도착합니다. — 보고가 올 때까지 계속 작업하세요. 판정을 보고하면...