跳转到内容

第 23 课:PR 工作流

把 agent 的代码修改整理成可审查的 branch、commit、diff、测试证据、PR body 和用户确认点。

PR 工作流不是“让模型写一段总结”。它是一条交付协议:先把任务隔离到 branch 或 worktree,再收集真实 diff 和测试证据,然后生成 commit 与 PR body,最后在危险动作前停下来让用户确认。

Pi 源码快照 61babc2 没有内置完整 PR agent。它提供的是更底层的能力:bash/edit/write 工具、extension 生命周期、session 记录、git checkpoint 示例和可扩展命令。课程要做的是在这些能力外层加一个 PR-ready harness,让 agent 的输出变成 reviewer 能放心打开的变更包。

上一阶段你已经能让 agent 修改代码、运行命令、生成 trace。现在问题变成:

  1. agent 修完 bug 后,怎么保证它不是直接污染主分支?
  2. PR 描述里的测试证据从哪里来,能不能被复核?
  3. commit、push、open PR 这些动作,哪些可以自动做,哪些必须让用户确认?

如果没有这套边界,agent 可能“看起来完成了任务”,但 reviewer 拿到的是一个缺测试、缺风险说明、甚至混入无关修改的 diff。

把 PR 工作流拆成六个控制点:

  • branch 控制点:任务开始前创建隔离分支或 worktree,记录 base branch 和任务 id。
  • change 控制点:agent 只能在授权路径内修改,并在结束时读取 git diff
  • evidence 控制点:每条测试命令都保存 command、exit code、摘要和关键输出。
  • commit 控制点:commit message 来自任务目标和 diff 摘要,但 staged scope 必须先检查。
  • PR body 控制点:PR body 不是流水账,而是 summary、verification、risks、review checklist。
  • user confirmation 控制点:push、创建 PR、合并、危险 rewrite 都是显式确认点。

这六个点串起来后,PR 工作流才是工程系统,而不是 prompt 模板。

1. 从 branch 开始,而不是从 prompt 开始

Section titled “1. 从 branch 开始,而不是从 prompt 开始”

真实 coding agent 不应该先写代码再想怎么交付。任务创建时就要记录:

type PrTask = {
id: string;
baseBranch: string;
workBranch: string;
prompt: string;
cwd: string;
};

baseBranch 用来计算 diff,workBranch 用来承载变更,cwd 用来约束命令执行位置。后续所有证据都挂到同一个 id 上。

PR body 的 summary 可以由模型草拟,但事实必须来自本地仓库:

git status --short
git diff --stat <baseBranch>...HEAD
git diff <baseBranch>...HEAD

教学时要强调变量流:baseBranch 进入 diff 命令,diff 输出进入摘要器,摘要器只能解释已经发生的文件变化,不能凭任务描述补不存在的实现。

测试证据至少包含四个字段:

type Evidence = {
command: string;
exitCode: number;
summary: string;
outputPath?: string;
};

exitCode 是机器判断,summary 是给 reviewer 快速阅读,outputPath 保存长日志。PR body 里可以只放摘要,但后台应该保留完整日志。

课程 lab 的入口是 /Users/ryanchen/codespace/pi-agent-course-lab/src/13-pr-workflow.ts。这个 starter 已经把 PR body 拆成 SummaryVerificationRisks 三段。

课堂上要把它扩展成更接近真实交付的结构:

## Summary
- 做了什么
## Verification
- `npm test -- login` -> exit 0: login tests passed
## Risks
- 可能影响哪些路径
## Review Checklist
- [ ] diff 只包含本任务相关文件
- [ ] 测试证据可以复跑
- [ ] 没有 secrets、token、私有日志

PR 工作流里有两类动作:

  • 可自动动作:创建临时分支、读取 diff、运行测试、生成本地 commit message 草稿。
  • 需要确认的动作:stage 大范围文件、commit、push、创建 PR、merge、force push。

确认点不是为了降低自动化能力,而是为了让责任边界清楚:agent 可以准备材料,用户确认后才把材料推向共享仓库。

Pi 的 auto-commit-on-exit.ts 示例说明 extension 可以在 session_shutdown 时检查 git status 并运行 git add / git commit。这不是课程推荐的最终 PR 策略,因为自动 git add -A 风险较高;但它证明了 Pi extension 能在生命周期事件里挂接 git 工作流。

更稳妥的 PR agent 应该把这个动作拆开:先收集 status 和 diff,再生成可审查计划,最后在用户确认后才 commit 和 push。

本课实践是把 starter 的 createPrBody() 扩成一个小型 PR packager。入口文件:

/Users/ryanchen/codespace/pi-agent-course-lab/src/13-pr-workflow.ts

第一步不要接 GitHub API,只处理本地材料:

const task = {
title: "Fix flaky login test",
baseBranch: "main",
workBranch: "agent/fix-login-test",
};
const diffSummary = ["Stabilized async wait", "Kept change scoped to test harness"];
const tests = [
{ command: "npm test -- login", exitCode: 0, summary: "login tests passed" },
];
const risks = ["Could hide a real timing issue if app behavior changes"];
const body = createPrBody({ title: task.title, summary: diffSummary, tests, risks });

变量流要讲清楚:

  • task 是用户输入和分支状态的结合,不来自模型自由发挥。
  • diffSummary 应由真实 diff 生成或人工确认。
  • tests 必须来自实际命令执行结果。
  • risks 可以由模型辅助枚举,但要绑定到 diff 中真实变更的文件和行为。

课堂上把 PR workflow 压缩成一段可讨论的伪代码:

async function preparePr(task: PrTask) {
await exec("git", ["switch", "-c", task.workBranch]);
const result = await runAgent(task.prompt);
const status = await execText("git", ["status", "--short"]);
const diff = await execText("git", ["diff", `${task.baseBranch}...HEAD`]);
const tests = await runEvidenceCommands(["npm run typecheck", "npm test"]);
const prBody = createPrBody({
title: task.prompt,
summary: summarizeDiff(diff),
tests,
risks: classifyRisks(diff),
});
return { status, diff, tests, prBody, needsUserConfirmation: true };
}

这段代码里有三个关键控制点:

第一,task.workBranch 在 agent 修改代码之前就存在,所以后续 diff 有边界。

第二,tests 不是 PR body 里的普通文本,而是由 runEvidenceCommands() 返回的结构化证据。命令失败时也要进入证据列表,因为失败本身是 reviewer 需要知道的信息。

第三,函数返回 needsUserConfirmation: true,表示它只准备 PR-ready 材料,不直接 push。课堂会让学生讨论:如果把 push 放进这个函数,应该增加哪些审批参数和失败回滚策略?

  • /Users/ryanchen/codespace/pi-agent-course-lab/src/13-pr-workflow.ts:本课 lab starter,包含 Evidence 类型和 createPrBody()
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/auto-commit-on-exit.ts at 61babc2:展示 extension 能在 session_shutdown 中读取 git 状态并提交。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/git-checkpoint.ts at 61babc2:展示基于 session 事件创建 git checkpoint,适合理解“代码状态”和“会话状态”要同步。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/examples/extensions/github-issue-autocomplete.ts at 61babc2:展示通过 gh issue list 读取 GitHub issue,并把外部研发对象接入输入体验。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/src/core/extensions/types.ts at 61babc2:定义 ExtensionAPIsession_shutdowntool_call 等 extension 边界。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/src/core/agent-session.ts at 61babc2AgentSession 负责事件订阅、session 持久化、工具注册和 active tools 管理,是 PR harness 接入执行证据的宿主。
  • /Users/ryanchen/codespace/external-sources/pi/packages/coding-agent/src/core/session-manager.ts at 61babc2:session JSONL 记录、custom entry 和 fork 信息可用于把 PR 证据归档到任务历史。

学完本课后,你应该能做到:

  • 解释 branch、diff、test evidence、commit、PR body 和用户确认点之间的顺序。
  • 设计一个 PR body 数据结构,并说明每个字段从哪里来。
  • 区分“模型生成摘要”和“命令产生证据”。
  • 指出 git add -A、push、merge、force push 为什么必须经过额外确认。
  • 能基于 /Users/ryanchen/codespace/pi-agent-course-lab/src/13-pr-workflow.ts 生成一个可审查的 PR body。
  1. 实践题:扩展 /Users/ryanchen/codespace/pi-agent-course-lab/src/13-pr-workflow.ts,给 createPrBody() 增加 Review Checklist,并把失败的测试命令也保留在 Verification 中。
  2. 思考题:如果 agent 自动 commit,但用户后来发现 diff 混入了无关文件,你会把防线放在 branch 创建、staging、PR body 生成,还是 push 前确认?说明原因。