
这是什么
一套给编码 agent 用的纪律装置,做成一个 Go 二进制,没有运行时依赖,也没有常驻服务进程。它给 agent 三样东西:一道它没法靠嘴绕过去的提交闸门、若干拒绝把没做完的事叫做做完的质检器,以及一条把每个逃逸 bug 变成已关闭类别的回路。闸门把「没跑成」按失败计数——工具缺失、超时,或者返回谁也无法解析的输出,结论是「未检查」而不是「干净」——而这个数字就写在闸门自己的收尾行里。质量链条在每一环都放了一个会拒绝的控制器:一次规格访谈、一份计划、一个用冲刺推进的 backlog,以及一份独立的待办清单,它的关闭动作在没有勾选完的验收标准与记录在案的证据之前一律拒绝。这个工具拥有的一切都存在仓库里同一个目录下,而仓库自己的那份文件整体压过内置默认值。2026-08-20 到 2026-09-23 的三十四天里发了二十个 release,530 次提交、211 个星,Apache-2.0。
谁做的一个人维护:530 次提交里有 514 次是他的,署名 Pascal Watteel,账号 piwi3910,邮箱 pascal@watteel.com。其余 16 次里,12 次来自两个机器人——github-actions[bot] 十一次、dependabot[bot] 一次——这就是「最近窗口里约十二次由机器人署名」这个说法的来源;剩下 4 次来自四个各提交过一次的人:RealPLU、Acroaticum、ToberoCat 与 qinghuanandejiangshi,其中三人用的显示名和账号名对不上。历史里抽出的共同作者尾注共 11 条,其中 6 条署名 github-actions[bot];除了 Claude Opus 5、Codex 与 Nadim Stub 这几个名字,抽取器还把散文片段当成名字返回,所以那份名单不能当作可靠的致谢表。
它是怎么搭起来的
组成 · 5一件由编码 agent 调用的命令行仪器,外加一层不管 agent 想不想都会被调用的钩子。二进制负责计算并报告,agent 对报告采取行动——因为把工具当成协作者的 agent 会用它们,而被悄悄覆盖的 agent 会绕过自己的执行框架。这个工具拥有的一切都放在仓库里的同一个目录下,而仓库自己对每一条规则的版本整体压过内置默认值,这就是一个项目能改阈值而不必 fork 工具的原因。由此长出两个后果。报告说「干净」就必须意味着检查真的跑过,于是每一个域都要带上通过和失败之外的第三种状态,而且它必须活到人类会读的那一行收尾里。而闸门是从命令行、从 git 包装器、从持续集成三处调用的同一个收集器,所以任何让本机结论与 CI 结论不一致的东西都是共享路径上的缺陷。同一个二进制还扛着流程层——规格、计划、带冲刺的 backlog、待办清单、决策记录——对 linter 来说这不寻常,也正是它做成了一个程序的原因。它周围是薄薄的适配层:一个负责取回并校验二进制的启动器、从同一份母本生成的各宿主规则文件,以及必须报告同一个版本号的各宿主清单。
- cmd/procoder/
- 整个引擎:14 个文件,包括一个 72,448 字节的命令面、参数解析、一个本地套接字服务、闸门汇报所走的收集路径,以及把前后两端钉在同一套答案上的对照测试。
- internal/
- 59 个包、284 个 Go 文件。各域并排放在一起——gate、format、lint、security、deps、docs、infra、testrun、bench、ciops、maintain、codeindex、copilot——流程层(spec、plan、backlog、ask、adr、debt、learn、lessons、principles)与管道层(gitx、store、config、hook、portability、releases)都在同一棵树里,而不是拆成几个程序。
- .procoder/
- 仓库自己的状态,以及它对「工作该怎么被记录」的主张:34 份共 341 KB 的规格、7 份计划(领头的是一份 37,591 字节的命令 API 计划)、311 个共 421 KB 的 backlog 文件(29 个 epic、6 个 milestone、25 个编号冲刺)、5 条决策记录、25 份待办文件、一份 23,975 字节的教训台账、一份 29,500 字节、还在等答案的决策文件,以及二进制要读的配置。
- docs/
- 目录下直接放着 18 个文件,其中 17 份是文档:一份 81,671 字节的命令参考,域、配置、可移植性与生命周期的参考页,以及三份专门用来限制说法的页面——诚实的局限、定位,还有一份把「有证据的前提」和「没有证据的前提」分开标注的研究页。站点是 MkDocs,自定义域名记在一个 19 字节的 CNAME 文件里。
- 宿主面
- 漂移检查盯着的 12 份规则副本:其中 10 份与仓库根部的 agent 文件逐字节相同,都是 14,504 字节,Cursor 那份 14,571、Kiro 那份 14,531。周围还有六份带版本号的宿主清单,一个 34 文件的命令目录(其中 33 个以完全相同的体积镜像进 Kilo 与 OpenCode 两棵树),一份 15,093 字节的 skill,给 Claude Code 和 Copilot 的钩子定义,以及写了两遍的启动器——一份 8,072 字节的 shell 脚本和一份批处理文件。
取舍,以及它替代了什么
把没跑成的检查按失败处理 替代 只报告工具能发现的问题
这是架构页上的第二条契约,而且它被写成一条关于诚实的规矩而不是关于覆盖率的规矩:工具缺失、超时或返回无法解析的输出,就是「未检查」,由闸门按失败计数,绝不折叠成「没有发现问题」,于是报告说干净就意味着检查真的跑过。格式化器的结论有三个取值而不是两个,也是同一个理由。
二进制负责计算,agent 负责写入 替代 直接改动代码、文件与状态
第一条契约,理由是用 agent 接下来会怎么做来说的:把工具当成协作者的 agent 会用它们,而被悄悄覆盖的 agent 会绕过自己的执行框架——而且每一处改动都留在同一个地方可供复核,也就是 agent 自己的动作。模板、给别的宿主用的规则文件、规格骨架与任务全都是打印出来给 agent 去写的;例外只有工具自己的状态,以及一次要问两遍的缓存清理。
在 CI 里构建二进制,首次使用时取回 替代 把它们和插件一起提交进仓库
架构页把交易的两面都写了下来。提交进仓库让安装离线可用,代价是每个 release 花掉 39 MB 的 git 历史;改动之后接受的是首次运行需要一次联网,以及取不到时钩子发出警告、让会话继续而不是让会话失败。发布作业从被打上 tag 的提交构建全部五个目标。
只对采纳了这个工具的仓库作答 替代 把整套规则套到 agent 走进去的任何仓库上
这是它自己的一条决策记录:完整检查集属于带有配置目录、或者带有指名该工具的 agent 文件的仓库;在别人的仓库里只有放到哪儿都成立的检查会跑——密钥、超大文件、冲突标记、垃圾文件、AI 署名行——而读内容的检查只看这次提交写下的那些行。每次运行都会说明自己处在哪种模式。
让漂移检查去对齐那个本来就把规则写对的一半 替代 加一个把宿主副本整个关掉的配置键
一个没有宿主副本的根 agent 文件会让漂移检查要求全部十二份,从而在只用一种 agent 的仓库里拦下每一次提交,而唯一的出路是删掉那个文件,那同时会关掉仓库确实保留的那些副本的漂移保护。这里选择让两半对齐;已采纳部分里漂移或读不出来的副本仍然拦提交:一份过期的规则文件是在告诉另一个 agent 一些这个仓库已经不再相信的事,而不存在的文件谁也没告诉。
依据docs/architecture.md(6,449 字节,三条契约的出处)、README.md(10,335 字节)、AGENTS.md(14,504 字节)、docs/honest-limits.md、docs/positioning.md、docs/research.md、docs/commands.md(81,671 字节)、commands/spec.md、.procoder/adr/ 下的五条决策记录、.procoder/github/LESSONS.md,编号 278 到 303 的 pull request 与 issue 正文,以及完整的 896 个文件树及其体积。
制作过程
6 个阶段- 01
一道把「没查过」算作失败的闸门
这个项目在任何功能之前先立下的规矩是:没跑成的检查不算通过。架构页上的第二条契约写得很清楚——工具缺失、超时,或者返回谁也无法解析的输出,结论是未检查,由闸门按失败计数,绝不被折叠成「没有发现问题」。于是格式化器的结论有三个取值:干净、未格式化、未检查,而闸门的收尾行把它们并排打出来:
procoder gate: 0 clean, 2 unformatted, 0 unchecked, 1 out of scope, 8 hygiene finding(s) (3 blocking)。这条规矩也推进到了更小的角落:一个没有 lockfile 的package.json是明确的「无法扫描」缺口,而一份读不出来的规则副本报告自己是 UNREADABLE 而不是「缺失」。闸门只有一条代码路径,procoder check、procoder git与持续集成调用同一个收集器,本机通过而 CI 失败被定义为 bug 而不是环境差异;完整检查集只属于真正采纳了这个工具的仓库,而每次运行都会说明自己处在哪种模式。它严到连自己仓库里那条依赖告警的修复都拦下过:3.6.0 报出的版本在工作树里根本不存在,于是删掉它们的那个提交被拒绝,修法是让依赖扫描停在自己的检出边界上。在它自己的仓库上,计数器读作 856 个干净文件、0 个未格式化、0 个未检查、20 个超出范围、172 条卫生问题。 - 02
纪律在哪里不再可选
跑闸门是 agent 自己的事,而两个生命周期钩子不是。写入钩子在每一次写入和编辑上触发,会话启动钩子在 agent 读到任何东西之前运行——这也是为什么这个项目限制的是钩子输出的体积,而不是它的诚实程度。宿主只内联前两千字节的钩子输出,其余写到文件里,于是钩子把整条消息压在这个上限之下,给格式化后的正文一个有限份额,并且丢弃放不下的部分,而不是截断它:半个问题结论的含义没人能信,半个格式化过的文件则可能被整份写回去。被丢掉的东西会被计数,并指名用
procoder check去看全部。第一条契约指向同一个方向:二进制负责计算、agent 负责写入,所以格式化器是把内容交出去给人审阅,例外是它自己的状态文件。这条承诺也正是它伤到自己的地方。格式化命令过去在文件需要改动时打印一行表头加内容,不需要改动时只打印表头,两种情况退出码都是零,而闸门自己的提示还在怂恿这条命令:procoder format f | tail -n +2 > f。维护者就是这样弄丢了两份文档——两份都以零字节提交。修法是把结论移到 stderr、永远把文件自己的字节放在 stdout,并关掉了底下那个更安静的版本:既然没有表头可以剥,同一条管道就会删掉文件的第一行真内容——于是闸门的提示被改写,同时加了一道兜底:对非空文件拒绝打印空内容。 - 03
把一类 bug 关掉的回路
自我学习的回路是这个项目自述里存在的理由。一道提 PR 之前的自审——用一组视角去读,其中包括对抗性视角与边界情况视角——负责尽早抓住「评审员那一类」的问题。仍然逃逸出去的东西会变成教训台账里的一条,那份台账是仓库里的一个文件,已经长到 23,975 字节;而把它写下来并不算结案:这条记录点名的那项改造,无论是 lint 规则、评审表上的一行,还是一个新的钉住行为的测试,都必须在工作被算作完成之前落地。再往下的机器人评审员是兜底的那张网,而不是那张网本身,它抓到的东西也不算白费:有一条命令专门收集 GitHub Copilot 的自动评审结论,把仓库代码的每一丝痕迹都剥掉,只有在终端上得到确认之后才把它们作为 issue 落下来,并把它们记为「未学会」,直到有人写出那项关掉这一类的改造。它自己 backlog 里的一份冲刺文件把检验这些仪器的演练命名为「它声称能抓的每一类缺陷各埋一个」,演练报告有 17,554 字节,六个脚本驱动它,而架构页写下了这场演练要强制的规则——抓不住自己埋下的坏样本的检查器不值得信任。这条回路也记下自己的洞:一份待办文件说这条回路写出的记录其实并没有被限制长度;而「没有静默的绿」这条规矩有自己的规格文件、自己的 epic,还有两个以它命名的测试文件。
- 04
一个二进制,没有运行时依赖,以及它的账单
实现是一个 Go 程序:59 个内部包、284 个 Go 文件,一个 72,448 字节的主命令,外加一个只为找到它而存在的启动器。交叉编译在 CI 里按 tag 做,发布时把二进制和一份校验和清单一并公布,启动器首次使用时只取当前机器需要的那一个,按公布的摘要校验后缓存到插件旁边,此后每次都直接执行它,钩子运行时不再需要网络。这个安排本身就是一笔被写下来的交易:二进制过去是提交进仓库的,这让安装完全离线,代价是每个 release 花掉 39 MB 的 git 历史;改动它的那条架构决策接受的是首次运行需要一次联网,以及取不到时钩子发出警告、让会话继续,而不是让会话失败。README 没跟上自己的架构页——它至今还写着二进制被交叉编译后与插件一起提交——这正是这个项目用在别处的「镜像与漂移」检查本该抓到的漂移。单二进制的收益是可读性:同一个仓库里带着约十五个宿主路径的规则文件、六份各自写着版本号的插件清单,没有常驻服务进程。代价写在两处小字里:两条 PR 都承认独立的 JavaScript 没有被运行,因为没有测试脚本;还有一个未关闭的 bug——3.6.0 上的自更新命令问过是否要从 3.6.0 升到 3.7.0,随后在 Ubuntu 22.04 上取校验和清单失败,升级没有发生。
- 05
三十四天里二十个 release
第一个 release 是 2026-08-20 的 v1.0.0,比仓库创建晚四天,而它在 release 列表里的标题是一句话而不是一个号码:1.0.0 — 「the interface is a promise now」。此后三十四天里又发了十九个,而第一周才是速度里最有意思的部分:第一天四个、第二天四个、2026-08-24 四小时内三个,接着 2026-08-25 的 v3.0.0——前一天下午刚发过 v2.0.0。六天里声明了三个大版本,之后节奏落成大致每周一次:2026-09-01 的 v3.5.0、2026-09-09 的 v3.6.0、2026-09-23 的 v3.7.0;而提交数讲的是另一半故事,530 次里有 499 次落在八月,九月只有 31 次。tag 背后坐着一个宁可拒绝也不打标签的控制器:它检查仓库列出的九个文件里的版本号、changelog 条目、干净的工作树、干净的闸门和通过的测试套件,把所有失败一次性列出来,成功时打印那行
git tag命令交给人类去执行。它自己从不打标签,整套流程写在一份发布文档里,那行 tag 命令只是第七步;而 3.7.0 的发布 PR 显示这种核对习惯已经伸进 changelog——它列出那九个文件、新条目,以及一处对上一个版本标题日期的更正,那个日期原本是错的。 - 06
一次外来修复、一串机器人升级,和两个会误导人的失败
外面的流量很薄:快照时有 22 个未关闭 issue,以及一位外部贡献者——他为基础设施闸门做的 OpenTofu 修复被维护者 rebase、补了两个提交之后落地,而且名字留在了提交上;回复里补了一句:之所以要补,是因为两个二进制都装着的机器上,同一个目录仍然被送给了错的那一个,也就是原来那个 bug 反过来又出现了一遍。其余大部分是机器开的:来自工作流令牌的升级 PR,每条都带着同一个提醒——它不会触发 CI,必须关掉再重开——而每条都被维护者用针对具体版本的验证回复过:核对公布的校验和、在隔离环境里安装、把真实命令跑一遍。这个项目自己的两个 bug 比它们的修复更值得读。一个守卫测试在 CI 上失败过一次,同一个提交重跑却通过;维护者把它记成 issue,而不是让一次变绿的重跑把问题关掉,直到测试运行器改成读结构化的测试输出才关闭——于是「打印出一行失败文本」再也不能被当成测试失败。另一个是 Windows 上 v3.6.0 那次 tag 的作业失败,报告说有两个调用者赢得了启动竞争;他的判断是锁是对的、测试是错的,因为每个赢家是在自己的 goroutine 内部释放锁的,而 Windows 只是把十个 goroutine 排到了那五毫秒窗口之外——别的平台恰好都塞了进去。
相关档案
全部档案 →第 067 号
Reticle
一个 MCP 服务器加一个只在开发期生效的 SDK:让编码 agent 从应用内部去读、去操作一个正在运行的 web 或桌面应用,然后给出判词和该改的文件与行号,而不是一张截图。
第 070 号
delegate-skills
一个技能包,给每一种编码 agent CLI 各配一份委派技能:编排方写好自足的任务书,另一条 CLI 去改真实工作树,而审查与提交留给人。
第 065 号
GSD Core
「Git. Ship. Done.」——一个元提示、上下文工程与规格驱动开发的框架,每个里程碑都重复同一条五步回路:讨论、计划、执行、验证、发版。重活被推给上下文全新的子 agent,主会话因此保持轻量;每一项决定都写进规划目录下的 Markdown 与 JSON,而不是留在对话里。