開啟一個 Pull Request (PR)
開啟一個好的 PR,在解決 Git 問題之前,首先是一個研究問題。審查者在看到的第一個畫面就會判斷作者是否了解該儲存庫——標題的語法、主體的結構、提交訊息的主題都透露了這一點——所以重點是:了解這個儲存庫的維護者如何撰寫,然後照著寫。
先選擇深度
快速通道 適用於「直接開啟 PR」、「直接合併」、「把它搞定」——以及任何變更小到研究成本會超過它所能節省的審查時間的情況。快速通道的步驟是:定位、撰寫、提交、交接監控。跳過風格偵查員(style scout)及其卡片、語音校準和散文檢查。不要詢問是否想要快速通道:使用者的措辭已經表明了意圖,而確認這件事正是使用者想要避免的延遲。
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 # 通道、分支、主幹、髒污狀態、github.mine、下游堆疊 PR 以及哪些缺少主體
gl repo view --json isPrivate,owner,viewerPermission,pullRequestTemplates,nameWithOwner,defaultBranchRef
ccx vcs guidelines # CONTRIBUTING、PR 範本、行為準則、Issue 配置——印出卡片路徑
然後執行 ccx vcs diff 來取得變更本身的內容。
ccx vcs lane 會報告通道——它不會選擇通道,你也不會。ccx vcs ship 會在提交時選擇 jj、git 或 gt,而使用者無法控制的儲存庫會靜默地從 gt 通道中排除,所以在 ccx vcs 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 卡片是為長篇部落格散文建立的:「永遠不要清單」和詞彙選擇可以完整轉移,語氣強度只轉移一半,長度和開場白則完全不轉移。完整的轉移對照表以及沒有設定檔時的後備方案都在 reference/voice.md。當儲存庫卡片與語音卡片不一致時,以儲存庫為準——你是他們格式中的訪客。
你不控制的儲存庫
github.mine: false 會改變語氣,並在撰寫前增加兩個檢查(詳見 reference/oss.md):
- 禮貌性語氣 — 關於變更本身要直接,關於決定則要謙遜:用數字支持主張,維護者保留他們該做的決定。
- 標準符合性檢查 — 將
ccx vcs diff的結果與指南卡片和檢查清單進行比對:範圍、風格、測試、文件、提交衛生、diff 大小與該儲存庫合併 PR 的常模進行比較。每個不符合項目都要修正,或是在主體中用一句話說明此偏離。 - Issue 優先 — 當指南卡片將 Issue 標記為硬性要求時,請在開啟 PR 前先搜尋:
gh issue list --repo <slug> --search "<keywords>" --state all。沒有符合的 Issue → 擬定一個 Issue 草稿,然後透過AskUserQuestion將草稿呈現給使用者——按草稿開啟 / 先編輯 / 連結現有 Issue / 刻意跳過此要求——並根據他們的選擇採取行動。開啟 Issue 會以使用者名義發布在別人的專案上;擬定草稿則不會。
撰寫
標題、主體和提交訊息主題應按照風格卡片所述信心水準來撰寫;如果存在 PR 範本,它應作為主體的骨架。在原本會使用形容詞來描述變更程度的地方,請使用數字:審查者可以驗證「冷啟動減少 40 毫秒」,但會質疑「快很多」。
用審查者無法驗證的形容詞自我評價,以及格式無法容納的愉悅語氣。 「設定載入不再為每個鍵重新讀取檔案——啟動時僅讀取一次,冷啟動減少 40 毫秒。」
說明變更及其可衡量的影響;審查者可以驗證而不是信任。
將主體和標題寫入檔案,絕不使用 shell 參數——包含反引號、$ 或程式碼區塊的主體無法在命令列上完整存活,而標題更會因為較小的問題就被拒絕(詳見「提交」章節):
BODY=$(mktemp)
TITLE=$(mktemp)
主體是散文,因此在提交前會通過散文檢查:slop-cop check "$BODY" --lang=markdown,對標記進行分類,而不是全部接受。
提交訊息也獲得相同的處理:卡片記錄了此儲存庫的主題語法、其長度以及其自身合併的提交攜帶了哪些 trailers——請僅匹配這些 trailers,不要添加其他。在歷史中沒有 Co-Authored-By: Claude 或 Generated with Claude Code 的儲存庫上,標記工具使用的 trailer 正是會讓一份用心撰寫的 PR 看起來像是自動化產生的一行,而維護者會據此過濾;在歷史中有這些 trailer 的儲存庫上,保留它們才是慣例。
每次工作階段一個堆疊
來自同一個工作階段的相關工作應放在一個連鎖的堆疊中,而不是在主幹上產生多個獨立的 sibling——即使這些 diff 證明是不重疊的。「檔案沒有重疊」不是分割的理由:sibling 會抹去堆疊所陳述的審查順序,並放棄 mid-stack 的機制——一次重新堆疊(restack)會將一個修改(amend)帶入其上的每個分支,而 sibling 則各自單獨 rebase。請依照依賴順序進行鏈接,某個項目消費了它上面的項目;仍在進行中的通道應放在最頂端,這樣它下面的已完成分支就不會在其變動下被重新 rebase。相關機制詳見 reference/stack.md。
一個 PR 是一個功能大小的工作單位。後續追蹤、修正和審查回應是對已開啟 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 或 shell 名稱配對的引數,例如 GitHub's ... git,而檔案中的標題永遠不會到達命令列。Ship 會從該標題取得提交主題,並從 PR 主體取得提交主體,因此只有在主題與標題不同時才需要 -m。
--no-watch 是因為 ship 內建的監視會阻塞直到 CI 完成——在你正等待的時候是正確的,但當監控即將交接時就是錯誤的。Ship 在所有通道中都擁有建立和更新 PR 的職責:與之並行的手動 gh pr create 會產生一個 gt 通道無法與其自身堆疊協調的 PR。
在一個已經開啟的 PR 上,ship 會發出警告並保留其現有的標題和主體,因此一個修改(amend)不攜帶這兩個旗標,而一個需要更改文字的 PR 則使用 --replace-pr-text。
在 gt 通道上,一次提交可能會建立多個 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,超時時間: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 時,再次 SendMessage 給監視器相同的 poll: 行,並再次執行檢查。如果再次失敗,向使用者報告監控未啟動。僅僅產生一個代理並不代表腳本正在執行。
每個 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 上既未在佇列中也未被驅逐。沒有任何審查者的最新審查是 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: "佇列標籤 <label> 已添加。在相同的狀態檔案上恢復佇列監控。")
它的報告會以通知的形式在工作進行中到達——繼續工作直到收到報告。當它報告一個判斷時…
…(其餘內容已被截斷,無法翻譯)