Penpot implement-plan 命令详解:从 Issue 到 Commit 的 AI Agent 端到端实现流水线
Penpot 仓库内置了一套面向 AI 编程 Agent(OpenCode)的自动化研发流程,其中 implement-plan 命令 是"计划已就绪"之后的执行入口:它在一次调用内串联起创建 GitHub Issue、创建 issue-NNNN 分支、按计划实现功能、按 Penpot 提交规范落盘 commit 四个环节。读完本文,你将掌握这条流水线的每一步设计意图、所依赖的 skill 与 workflow memory 的协作关系,以及 Penpot 为 Agent 设定的安全边界(如"永不 push"),并能在自己的仓库中参考同样的模式组织 AI 辅助开发流程。
命令定位:一次"计划就绪"后的全链路执行
implement-plan 的完整定义位于 .opencode/commands/implement-plan.md,其 YAML frontmatter 声明了它的基本属性:
description: Execute a ready plan end-to-end — create a GitHub issue, branch issue-NNNN, implement the plan, then commit via the create-commit skill
agent: build
两个关键点值得注意:
agent: build:该命令在具有写代码能力的 build agent 下运行(对照同目录的 resolve-git-conflicts.md 也使用 build agent,而 review.md 则明确要求"不修改任何代码")。- 无额外参数:文档原文明确写道——"This command is run once a plan is ready (for example, from plan mode). Execute the plan already prepared in the current session context — it does not take extra arguments." 也就是说,命令的全部输入来自当前会话上下文中已经准备好的计划,它不负责重新规划,只负责把计划端到端地执行完毕。
这与上游的 planner skill 形成清晰的职责切分:planner 扮演"只读的高级软件架构师"角色,产出包含 Context、Affected Modules、Approach、Risks、Testing 五个部分的结构化实施计划,并将其保存到 .opencode/plans/YYYY-MM-DD-<title>.md;而 implement-plan 接手之后的第一件事,就是把这个计划"物化"为一个可追踪的 Issue,再围绕 Issue 组织分支与提交。整个链条可以概括为:
planner(只读规划)
└─> implement-plan 命令
├─ 1. create-issue skill → 获得 Issue 编号 NNNN
├─ 2. git checkout -b issue-NNNN
├─ 3. 按会话中的计划实现代码(不提交)
└─ 4. create-commit skill → 规范 commit,不 push
步骤 1:创建 Issue —— 把计划转成可追踪的研发单元
命令的第一步是"Use the create-issue skill, following the Creating Issues from Draft Body flow in mem:workflow/creating-issues. Derive the issue title and body from the plan." 这一步有两条硬性要求:
- 标题与正文必须从计划派生,而不是直接复用计划文件名;
- 必须捕获新 Issue 的编号,文档把它记为 NNNN——因为这个编号同时决定分支名和后续 commit 的引用格式,是整条流水线的"主键"。
create-issue skill 本身只是一个薄入口(见 create-issue/SKILL.md),所有真实规则——标题派生、元数据策略、正文模板、Issue Type ID——都集中存放在 workflow/creating-issues.md 这份 workflow memory 中,implement-plan 指定的正是其中的 "Creating Issues from Draft Body" 流程(没有现成 PR,从计划/草稿正文出发建 Issue)。这份 memory 提供了相当具体的可执行细节,构成 implement-plan 第 1 步的完整操作手册:
标题派生规则
- Bug 标题用描述性现在时,格式为
[Where] [present-tense verb] when [condition],例如 "Plugin API crashes when setting text fills";禁止以 "Fix" 等祈使动词开头。 - Feature / Enhancement 标题用祈使句,格式为
[Imperative verb] [what] in/on [where],例如 "Add customizable dash and gap length controls to dashed strokes in the sidebar"。 - 通用规则:必须写清"where"(UI 位置或模块);剥离
bug:、feature:、:bug:等前缀;纯文本、不用 emoji;一个描述里若含两个相关问题,用 "and" 把两者都写进标题。
元数据与正文模板
| 字段 | 规则 |
|---|---|
| Labels | 社区贡献者 PR 加 community contribution;不加 bug/enhancement 标签(用 Issue Type 代替) |
| Milestone | 当前或下一个计划里程碑,不确定则省略 |
| Project | 固定为 Main(project number 8),用 --project "Main" |
| Issue Type | gh issue create 无法直接设置,需创建后用 GraphQL 补设 |
正文则写入临时文件以避免 shell 引号问题,Bug 与 Enhancement 各有固定模板(Description / Steps to reproduce / Expected behavior / Affected versions,或 Description / Use case / Affected versions),并且要求不要软换行段落——每段在源码中保持单行,换行只用于结构性分隔(章节标题、列表项、代码围栏、空行),以便后续 diff 更干净。
建 Issue 的实际命令
cat > /tmp/issue-body.md << 'ISSUE_BODY'
<body content here>
ISSUE_BODY
gh issue create \
--repo penpot/penpot \
--title "<Derived title>" \
--label "<label>" \
--project "Main" \
--body-file /tmp/issue-body.md
命令输出形如 https://github.com/penpot/penpot/issues/<NUMBER>,这里的 <NUMBER> 就是 implement-plan 文档所说的 NNNN。若需要设置 Issue Type,则先通过 GraphQL 查询拿到 Issue 的 node id,再执行 updateIssue mutation 设置 issueTypeId(memory 中列出了 penpot/penpot 仓库六种 Issue Type 的 ID 表,以及"Bug report → Bug、feature request → Enhancement/Feature、文档 → Docs、其余 → Task"的映射规则),最后用 gh issue view <NUMBER> --json ... 验证标题、标签、里程碑与项目归属。
步骤 2:创建以 Issue 命名的分支
Issue 创建成功后,命令要求立即切换到以 Issue 命名的分支:
git checkout -b issue-NNNN
其中 NNNN 替换为步骤 1 捕获的 Issue 编号。这个命名约定让分支与其追踪的 Issue 一一对应:从分支名即可反查该次改动对应的需求单元,也为后续 commit 中的 Closes #NNNN 引用建立了可校验的锚点。值得注意的是,implement-plan 只要求"创建并切换",而不要求任何 push 或远端操作——这与 AGENTS.md 顶部的 HARD RULES 一致:"Never git push, force-push, or modify git origin... The user pushes from their own shell." Agent 的全部活动被限制在本地工作区内。
步骤 3:执行计划 —— 只实现,不提交
第三步是流水线的主体:"Implement the prepared plan from the session context. Work methodically, keeping changes focused on what the issue requires. Do not commit — the commit happens in step 4."
这里有两层设计意图:
- 聚焦实现:改动严格限定在 Issue 所要求的内容上。上游 planner 在生成计划时已经按"垂直切片"原则把任务拆成 XS/S/M 级(每个任务不超过约 5 个文件)、附带验收标准与验证命令,并依据 monorepo 依赖图(
frontend -> common、backend -> common、exporter -> common、frontend -> render-wasm)排定顺序,执行阶段要做的就是逐任务落地并逐段验证。 - 提交权后置:实现阶段禁止
git commit,把"何时提交、如何写 message"收敛到第 4 步的单一入口。这避免了实现中途产生碎片化提交,也保证最终提交信息能一次性准确概括全部改动。
同时,AGENTS.md 要求 Agent 在编码前先读受影响模块的 core memory(如 frontend/core、backend/core、common/core),并"Never pipe test output directly to filters"——测试输出必须先重定向到文件再检查,以防隐藏失败。这些约束虽然不写在 implement-plan 文档内,却是该命令执行环境的一部分。
步骤 4:通过 create-commit skill 落盘提交
实现完成后,命令要求"load the create-commit skill and follow its workflow to commit the changes",并特别指出要提供三样东西:
- 一段简要总结——实现了什么、为什么;
- Issue 引用(
issue-NNNN); - 当前运行的模型名称,以便正确写入
AI-assisted-bytrailer。
create-commit skill 的职责边界写得很清楚:它"owns the commit format, staging review, and safety checks — it does not implement features or push"。其工作流为:
- 暂存调用上下文指定的文件(不向用户确认);
- 运行
git diff --staged复查内容——若发现密钥、.env值、调试打印或与声明意图不符的内容,立即停下并告知用户; - 按规范起草 message(正文每行 72 字符换行),执行
git commit -m "<subject>" -m "<body>"(正文含特殊字符时可用git commit -F -); AI-assisted-bytrailer 的取值由调用上下文提供,原样使用。
commit message 的完整规范在 workflow/creating-commits.md 中定义:
:emoji: Subject line (imperative, capitalized, no period, <=70 chars)
Body explaining what changed and why.
Wrap lines at 72 characters — git log and tooling
render long lines poorly. Keep each line concise.
AI-assisted-by: model-name
要点包括:
- Emoji 类型菜单:
:bug:bug fix、:sparkles:enhancement、:tada:new feature、:recycle:refactor、:lipstick:cosmetic、:ambulance:critical fix、:books:docs、:wrench:config、:zap:perf、:whale:docker、:fire:removal、:globe_with_meridians:translations 等共 17 种。 - Issue 引用格式:使用
Closes #NNNN(而不是Fixes #NNNN)将 commit 关联到 GitHub Issue。 AI-assisted-by规则:只写裸模型名(如mimo-v2.5),不加opencode-go/之类前缀。- 身份来源:不猜测、不使用
--author,作者身份取自本地 git config。
create-commit 还附带一组硬约束:不 push、不运行 git reset/git checkout/git restore/git clean/rm、不改作者身份、未经明确要求不 amend 本会话之外的提交、不绕过 pre-commit hooks、不添加本会话未创建的未跟踪文件。
安全边界:流水线在哪里"停下"
把整条命令串起来看,implement-plan 的终点是本地的一次规范 commit,其最后一句明确写道:"Do not push. Pushing is handled separately by the user." 即推送动作永远留给用户在自己的 shell 中完成。这一设计与 AGENTS.md 的硬规则(永不 push、永不修改远端、已推送的 commit 未经明确要求不 amend)共同构成了 Agent 的版本控制安全边界:Agent 负责 issue 化、分支、实现与提交这些可本地验证的环节,而所有影响远端仓库的操作都保留给人类。
小结:四个环节与它们的支撑文件
| 环节 | 动作 | 关键支撑 |
|---|---|---|
| 上游 | 生成结构化实施计划(只读) | planner skill、计划存至 .opencode/plans/ |
| 1 | 从计划派生 Issue,捕获编号 NNNN | create-issue skill、creating-issues memory |
| 2 | git checkout -b issue-NNNN |
分支名与 Issue 一一对应 |
| 3 | 按会话中的计划实现,不提交 | 计划中的任务切片、验收标准与模块验证命令 |
| 4 | 按规范提交(emoji 前缀、72 列正文、Closes #NNNN、AI-assisted-by),不推送 |
create-commit skill、creating-commits memory |
implement-plan 的价值在于它把"AI 写代码"中最容易失控的环节——需求追踪(Issue)、隔离(分支)、质量收敛(单次规范提交)、责任边界(不 push、不篡改作者身份)——固化成一条不可跳步的流水线,每一步的格式细节都下沉到可独立维护的 skill 与 memory 中。对于希望在自己仓库中搭建类似 AI 辅助研发流程的团队,这套"command 只做编排、规则下沉到 skill/memory、推送权保留给人类"的划分方式是一个值得直接参照的模板。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00