核心引擎 · 难度:进阶

ce-babysit-pr tick

看护一个打开中的 PR 的长跑 tick 引擎。三种姿态、8 步 tick、分支保鲜规则、settle 判断、托管 stack 巡游。

唯一的 PR 看护循环

ce-babysit-pr 是本插件中唯一的长跑 tick 循环。它每次 tick 跑一次,在真正停止(terminal / looks-ready / budget / blocked-external-drained)和常驻残余(needs-human / blocked-failing / stack-blocked,阻断 ready 宣告但不结束循环)之间做判断。它把反馈委托给 ce-resolve-pr-feedback,把CI委托给 ce-debug,把描述刷新委托给 ce-commit-push-pr。它自身不开启 CI 看护、不自跑轮询,也永远不说"可以合并"。

三种姿态

每次运行选择一种姿态,写成 posture:target|stack-ready|stack-land。每次托管 stack --continue-invocation 都要重新声明。

姿态行为会合并吗?
target只看指定的那一个 PR。在 looks-ready 处停止。若确认的托管 stack 还有工作要做,仅主动提议一次 stack-wide;用户拒绝则保持 target-local。从不
stack-ready一旦当前活跃层进入 静止态(零可执行积压 + 无常驻残余 + 无进行中的委托),上移到下一个打开的、非草稿的、更上层的层。下方各层留在 downstack 探针之下;最底下一层若重新打开,就把巡游拉回来。从不
stack-land巡游行为与 stack-ready 相同。选择它即是合并授权。一旦最底部的打开层看起来 ready:gh run <that-PR> --yes --squash + gh stack sync。然后继续。会(最底部打开且已 settle 的层)

选择方式:只点了一个 PR 而无 stack 措辞 → target(但若存在确认的多层托管 stack,问一次);显式 stack 意图 → stack-ready;"landing-and-merge" 意图 → stack-landmode:pipeline 永不询问。

8 步 tick(顺序是不变量)

一次 tick = 严格按以下顺序执行。顺序就是契约。

ce-babysit-pr 看护循环 — 一次性 opt-in 后连续 tick 等待 CI/反馈/新 commit 1 · 终态检查 MERGED / CLOSED → 停止。(例外:stack-land 之后的 MERGED 是一次过渡。) 2 · 抓取 head SHA + 托管 stack 基线 要求工作区干净 + 仍是确认的 manager 成员 3 · 先于 CI 处理反馈 线程或非线程评论 → ce-resolve-pr-feedback mode:pipeline(仅一次) 4 · 过期 SHA 取消 head 已变 → 跳过本次快照的 CI(新的运行将在下次 tick 出现) 5 · 对当前 head 跑 CI flaky/infra → gh run rerun;真实失败 → ce-debug mode:pipeline 6 · 分支保鲜 消费快照中确切的那一条——绝不臆测 7 · 托管 upstack 维护 在确认的 stack 层上发生一次委托推送之后 8 · 下次 tick 开头重新快照
8 步 tick。顺序是不变量。步骤 1 与 4 可提前结束本 tick。

各步骤详解

步骤 1 — 终态检查

快照中 pr_stateMERGEDCLOSED → 停止并报告。唯一例外:当本次运行刚刚完成对该 PR 的已授权 stack-land 合并时,把 MERGED 视为托管 stack 的层过渡,而不是运行级的终态停止。

步骤 2 — 抓取基线

记录快照的 head_sha;对确认的托管 stack,还要从 gh stack view --json 记录一条可恢复的基线:在目标分支之上或同一层级的、由 manager 排序的打开分支,以及每个分支的 remote-tracking OID。工作区干净 + 仍是确认的 manager 成员是前提。

步骤 3 — 先于 CI 处理反馈

counts.threads > 0counts.comments > 0,调用 ce-resolve-pr-feedback mode:pipeline <pr-ref> 恰好一次。当 trajectory 触发器命中(invariant_rounds[]. = 2、积压上升、重复簇等)时传入 trajectory。每个 tick 仅做一轮 resolve——绝不扇出。

对每一条返回的类型化 needs-human:在 ## Needs Needs your decision 下渲染完整 payload,通过 pr-snapshot mark --residual-file <path> --disposition needs-human 持久化,保留被覆盖的线程为打开状态。

每一条你传入但没有返回类型化残余覆盖的评论进行核对:mark --disposition dispatched(若是 fix 结果则用 --invariant-key)。

步骤 4 — 过期 SHA 取消

将抓取的 head SHA 与当前 SHA 对比。若它移动过,说明评论轮(或别人)推送了;本快照里的 CI 失败都针对的是一个过期的 SHA——不要据此行动。新的运行将在下次 tick 出现。

步骤 5 — 对当前 head 跑 CI

所有可执行的失败检查聚合成一轮修复处理。不要按检查逐条派发。

步骤 6 — 分支保鲜

消费快照所发出的、当前 branch_currency 那一条;没有条目就不要做任何 base-into-head 变更。完整路由、声明生命周期以及 BEHIND/DIRTY 机制详见 references/branch-currency.md

步骤 7 — 托管 upstack 维护

在确认的托管 stack 上发生一次委托推送之后,维护 upstack:重新执行 gh stack view --json,从其 tracking remote 拉取目标,验证目标的本地 head + remote-tracking tip 仍等于已推送的 SHA,然后执行 gh stack rebase "<first-dependent-branch>" --upstack --no-trunk --remote <tracking-remote> + gh stack push --remote <tracking-remote>(永远不要用原始 git push --force)。若发生冲突,立即 gh stack rebase --abort 并上报一个 needs-human/stack-sync 残余——不要在另一 PR 层上决定冲突语义。

步骤 8 — 重新快照

任何变更之后,在下次 tick 开头重新快照,并传入相同的 --invocation-id--session-started-at--invocation-budget-seconds。head SHA 与 CI 全集已变;调用级的预算未变。不要在一次 tick 中途再跑一次 snapshot 来重新派生 CI——这正是造成过期 SHA 混乱的根因。

停止条件

类别条件效果
真正停止终态 — MERGED / CLOSED看护结束
真正停止看起来可以合并 — 7 项条件全部满足(见下)看护结束
真正停止外部受阻已耗尽 — fork-PR CI 审批门,已排干看护结束
真正停止预算 — 活跃预算(默认 8h)或 3 天底线已达看护结束
常驻残余needs-human阻塞 ready;不停止循环
常驻残余blocked-failing阻塞 ready;不停止循环
常驻残余stack-blocked阻塞 ready;不停止循环

"看起来可以合并" — 7 项条件必须全部满足

  1. mergeability_certain + mergeable == "MERGEABLE" + merge_state_status == "CLEAN"
  2. base_ref_blocker == null
  3. checks_terminal 为真(无仍在运行的任务)
  4. 零可执行积压:counts.threads == 0counts.comments == 0
  5. open_needs_human == 0(被延迟的"或未通知"线程算 ready)
  6. branch_currency_blocker == null
  7. quiet_seconds ≥ settle 阈值(默认 300s),且"评审仍在路上"检查通过

Settle 窗口规则

评审仍在路上检查(即判断)

Settle 窗口说 PR 不再活动;它说没有评审在路上。看这些:

一个已宣告(👀 / "reviewing…")但两边都没出东西的评审者,才是真正无法判定的情形。有限等待后,坦率说出你无法确认的内容。

报告

一行固定首行状态,然后是一段读者无需回翻就能据此合并的回顾:

✅ Looks merge-ready — <one-line evidence>. Your call to merge.
🟡 Cautiously looks ready — <evidence + caveats>
🎉 Merged — <one-line evidence>
🚫 Closed — <evidence>
⛔ Blocked — <evidence>
⏱️ Budget — <active|backstop, elapsed>
⏸️ Paused — <reason>

永远不要说 "safe to merge"(references/report.md)。回顾应点明反馈主题与结果、CI 修复、推送、运行时长、暂存项,以及为用户做的任何判断。

needs-human 契约

ce-debugce-resolve-pr-feedback 共享的类型化契约:

防"狼来了"收敛

trajectory 字段是事实而非判定——看护把它们交给被委托的叶子,由叶子判断是否收敛:

触发器含义
check_recur_max >= 2同一个 check 一再复现
stream_alternations >= 3线程与 CI 已交替 ≥3 次
上升的 unresolved_trendnew_threads_this_tick & &> 0积压在上升的同时仍有新线程到达
heads_since_progress >= 2两个 head SHA 之间未取得任何已 settle 的进展

当任一触发器命中时,叶子做出变更之前把 trajectory 传给叶子;由叶子决定这是普通进展还是真正的非收敛。叶子可能返回一个 needs-human 残余,把整条流挂起(例如浮现的 CI 权衡、第三轮 invariant)。永远不要自己宣告非收敛。

自维持 vs 检点模式

看护支持两种执行模式:

预算算术

预算上限会重置吗?
活跃看护时间默认 8h(用户在入口可覆盖)重新加注与确认的托管 stack 层过渡必须匹配原始 ID、起始与预算——不重置,不延长
墙钟底线3 个日历日从不重置(兜底)
死时排除挂起时间(合盖等)从活跃预算中扣除粗粒度检测:宽于阈值的间隔视为死时

重新加注会保留 last_change_atinvocation_started_atinvocation_budget_seconds——任一计时器都不能被重启或延长。

参考