开源Sandcastle项目实战:AI独自写889次代码更新全流程揭秘


震惊!889次代码更新全是AI写的,人类只负责看!这个Sandcastle项目证明AI能独自写完889次代码更新,人类全程没写一行。它靠的不是魔法,而是一套严格的分工流程:AI写、AI查、AI再审,人类最后拍板。

这个Sandcastle项目说白了就是一个大实验。实验想证明一件事:AI不需要人类一行一行写代码,也能走完整个软件开发流程。关键不是AI多聪明,而是把流程设计好。AI负责写代码,AI负责检查自己写的代码,再来一个AI负责重新审核一遍。人类只在最后看一眼,保证大方向没错。最后结果出来了,889次代码提交,全是AI干的。这件事的意义比项目本身大得多。软件开发正在从敲代码变成设计流程和写规则。

技能系统是整个事情的起点

项目作者本身在搞一个叫mattpocock skills技能系统的东西。你可以把它理解成一套操作说明书。不是给人看的,是给AI看的。作者把复杂的开发流程拆成很多小块,每一块就是一个技能模块。每个技能模块里面写了很详细的结构化提示词,还有一堆行为规则。

用人话解释就是,作者把程序员脑子里的经验写成AI能看懂的操作步骤。比如写代码技能里面规定了怎么命名变量、怎么加注释。修bug技能里面规定了先定位错误再提交修复。写测试技能规定了测试覆盖率必须超过多少。做代码审查的技能规定了哪些写法算违规。把这些技能拼在一起,就像一条程序员流水线。

Issue就是任务说明书不是随便写的

这个项目有个特别关键的细节。所有的开发需求都写在GitHub的issue里面。英文里issue就是工单的意思。这些工单不是随便写两句就完事的。每个issue里面必须有明确的功能需求,必须有明确的验收标准,必须有明确的修改范围。比方说不允许写“优化一下性能”这种模糊的话,必须写清楚是哪段代码、从多少毫秒降到多少毫秒。

这样做的好处是AI不是自由发挥瞎改一通,而是在执行一张非常具体的工单。很多人用AI写代码失败就是因为没给标准答案的格式。你让AI自己理解需求,它理解出来的东西可能七拐八拐。但你把需求写成工单格式,AI就只能乖乖照着做。这就好比工厂里面的生产指令单,上面写什么就做什么。

AFK代理就是人不在AI继续干活

AFK这个缩写英文全称是Away From Keyboard。翻译成人话就是人不在电脑跟前。这里是说AI代理会自己轮班干活。整个流程大概是这样的。AI先去读取最新的issue,看看里面写了什么任务。然后去拉代码仓库里最新的代码。接着按照任务要求修改代码。改完之后自己提交一次代码更新。提交完之后自动触发一系列检查。

整个过程人类完全不参与。你说你可不可以半夜睡觉的时候让AI自己干活?可以。你说你出去吃个饭回来,AI已经提交了好几次代码更新?也可以。这就是AFK的意思,人不用守在键盘前面。AI自己就能把活干完并且把结果放好。

三层审核机制才是核心

重点来了,Sandcastle的灵魂是三层审核机制。很多人以为AI写代码就是它写出来你就直接用,那就大错特错了。

第一层叫自动化检查。英文叫Continuous Integration,简写CI。这一层做的事情说白了就是机器规则检查。跑一遍单元测试,看看有没有哪个功能断了。跑一遍类型检查,看看变量类型有没有传错。跑一遍代码格式检查,英文叫lint,看看缩进空格命名风格对不对。跑一遍构建验证,看看能不能正常打包。只要有任何一条不符合规定,这次提交直接打回去重做。

第二层是AI自动代码评审。每一个代码提交都会被另一个AI重新审核一遍。这个AI不负责写代码,只负责挑毛病。它会看看代码结构合不合理,有没有明显的逻辑漏洞,有没有违反之前定好的那些技能规则。这一步很多人会忽略,但这一步恰恰是关键。因为AI写代码的时候可能自己没发现错误,但另一个AI专门盯着看,就能找到问题。相当于你在公司里面写完代码,同事帮你做一次代码审查,只不过这个同事也是个AI。

第三层才是人类做的拉取请求审核。拉取请求英文叫Pull Request,简写PR。人类在这层做什么呢?不写代码,不改细节。人类只看方向对不对,看看这次改动符不符合产品目标。比如AI写了一个排序功能,人类看一眼说我们要的是倒序不是正序,那就打回去。人类只做战略判断,不做具体编码。

小总结一句人话

Sandcastle根本不是一个AI写代码的项目。它是一个AI流水线开发系统的验证样本。把写代码、查代码、审代码这三件事全部分开,交给不同的AI去做,最后只让人拍板。这才是它真正在干的事。

那么,Sandcastle这个项目到底是干嘛的

说完怎么来的,再说它是啥。Sandcastle的本质是一个AI代码代理调度系统。再说细一点,它是用来管理一堆AI,让它们在隔离环境里面自动写代码、改代码、合并代码的一套系统。

它解决的核心问题是什么呢?

AI写代码最大的痛点不是能力不够,而是几个特别恶心的坑。
第一,多个任务同时进行的时候会互相污染,一个AI改了文件,另一个AI不知道,直接覆盖掉。
第二,AI改代码特别容易把整个项目搞崩,它可能删掉一个关键函数,然后其他所有功能全炸。
第三,没有一个稳定的执行环境,AI写完的代码跑不起来,你都不知道是代码错了还是环境错了。
第四,很难让多个AI同时干活,同时做事就会打架。

Sandcastle就是专门解决这四个坑的。

Sandcastle是一个TypeScript库,专门用来在隔离沙盒里调度AI编程代理。你只需要调用一个run方法,它自动处理沙盒、分支和合并,支持Docker等三种内置环境。


1、你调用它就一句话的事
用Sandcastle干活简单到什么程度呢?你只需要在你的代码里写一行sandcastle点run,把配置参数传进去,剩下的就不用管了。不需要你手动去创建Docker容器,不需要你手动去建git分支,不需要你手动去合并代码。你也不用担心AI改代码的时候把环境搞崩了,因为Sandcastle已经在沙盒里把AI关起来了,随便折腾也影响不到外面。

2、Sandcastle帮你兜底三件脏活
具体来说,Sandcastle帮你做了三件你自己做会很烦的事情。
第一件是沙盒隔离,英文叫sandboxing。它给AI一个独立的小黑屋,里面什么东西都有,但AI只能在里面玩,碰不到你主机上的真实文件。
第二件是分支策略,英文叫branch strategy。你可以配置AI是直接写在主分支上,还是建个临时分支干完再合并,还是写到某个固定分支上。
第三件是自动合并,AI在隔离环境里写完了代码,Sandcastle负责把这些改动合并回你真正的代码仓库里。你不需要手动去git merge,也不需要解决那些乱七八糟的冲突,除非冲突大到自动解决不了。

3、它不挑环境你可以自己换
Sandcastle有一个很重要的设计,它在英文里叫provideragnostic,翻译过来就是不对具体的运行环境做绑定。

什么意思呢?就是说你不用因为用了Sandcastle就被锁死在某一个容器技术上面。

它自带了三种内置的运行环境提供者:
第一种是Docker,就是最常用的那个容器。
第二种是Podman,这个跟Docker命令基本一样但是更安全一些。
第三种是Vercel,这个是云平台用的那种。

如果你觉得这三个都不够用,你还可以自己写一个提供者,只要符合它规定的接口就行。

4、拿它来干什么最合适
Sandcastle不是拿来写一个小功能的工具,它是拿来搞事情的。

适合用它干三种事:
第一种事是并行跑很多个AFK代理,就是人不在电脑前AI自己干活的那种。你可以同时开十个AI去做十个不同的任务,互不干扰。
第二种事是建立代码审查流水线,让AI写完代码之后自动触发另一套检查流程。
第三种事更基本,你就是单纯想调度自己写的几个AI代理,让它们有秩序地干活,不要打架。

这三种场景Sandcastle都适合做搅屎棍。

Sandcastle的三种分支策略

Sandcastle的分支策略有三种:直接写主目录、建临时分支再合并、指定固定分支。

提示词系统强制二选一,文件模式才支持变量替换和命令执行。

Sandcastle在分支这块给了三种玩法。
第一种是直接在主目录干活,不拐弯抹角。
第二种是建个临时分支干完活再合并回去,完事儿自动删掉。
第三种是明确指定一个分支名,干活直接往那个分支上堆代码。

这三种模式靠沙盒提供者来配置,你只需要在运行的时候选一种就行。

第一种策略叫直接写主目录
这种策略的英文名叫Head,配置的时候写大括号里type后面跟head。它的工作方式很简单,AI直接在你当前的工作目录里改代码。没有额外的工作区,没有分支拐弯,没有中间商赚差价。这是bindmount这种挂载方式的默认行为,比如你用docker做沙盒的时候,默认就长这样。什么叫bindmount呢?就是把宿主机的某个目录直接挂进容器里,容器里改文件就等于直接改你电脑上的文件。所以不需要同步,改完就完事。

第二种策略叫先建临时分支再合并
这种策略的英文名叫mergetohead,配置的时候大括号里type后面跟merge-to-head。Sandcastle会先创建一个git工作区,英文叫worktree,在里面建一个临时分支。AI在这个临时分支上干活,想怎么改就怎么改。干完活之后,系统自动把临时分支上的所有改动合并回你当前的主分支,也就是HEAD指向的位置。合并完之后,那个临时分支会被自动删掉,工作区也清理干净。你看起来就像AI直接在主分支上干活,但其实它中间绕了个弯,只是你看不到而已。

第三种策略是指定固定分支
这种策略的英文名就叫branch,配置的时候大括号里type后面跟branch,然后再加一个branch字段写明分支名字。比如你写branch后面跟foo,AI干完活之后,所有代码提交都会落在foo这个分支上。这种模式适合那种你明确知道要往哪个分支干活的情况,比如你专门建了一个ai开发分支,让AI所有改动都堆在那里,你最后再人工筛选合并。

这些策略怎么用?
从你的角度看,用法非常简单。你在调用run方法的时候,里面配一个branchStrategy参数,写上大括号type冒号branch逗号branch冒号foo。就这一行配置,剩下的Sandcastle全包了。跑完之后你就能在foo分支上看到AI提交的代码。而且这一切都是百分百在本地完成的,不上传任何数据到云端。

提示词系统强制二选一

说完分支再说提示词。Sandcastle的提示词系统很死板,但你得接受这种死板。你必须提供且只能提供一种提示词来源。

第一种是直接写字符串,就是prompt字段后面跟一堆英文引号包起来的文字。
第二种是指向一个文件,就是promptFile字段后面跟一个文件路径。

你绝对不能两个都写,两个都写的话系统直接报错不干活。反过来你一个都不写,run方法也会报错,告诉你必须选一个。

Inline提示词就是纯文本
如果你选了promt写字符串这种方式,那就是最原始的纯文本模式。没有花括号双花括号的变量替换,没有感叹号开头的命令执行,也没有内置的分支名注入。系统不会帮你把某个位置替换成当前分支名,也不会帮你执行shell命令拿到结果再拼进去。你想用变量怎么办?自己用JavaScript拼。比如你想让AI干活的提示词里包含当前分支名,你得先拿到分支名变量,然后写反引号加美元符号大括号那种模板字符串拼出来。另外注意一点,如果你用了inline提示词,就不能同时传promptArgs参数,传了就报错。想用变量替换就必须改用文件模式。

文件模式才有完整能力
上面说的那些变量替换和命令执行功能,只在你用promptFile这种方式的时候才生效。你写一个markdown文件,里面可以用花括号双花括号写变量占位符,也可以用感叹号开头写要执行的shell命令。系统会先做变量替换,再执行感叹号命令,把输出结果填进去,最后把整个拼好的文本交给AI。

脚手架生成的文件只是方便
Sandcastle提供了一个初始化命令,你运行sandcastle init之后,它会帮你搭一个脚手架目录。具体来说它会在根目录下建一个.sandcastle文件夹,里面放一个叫prompt.md的示例文件。同时它生成的模板代码里会明确用promptFile指向这个.sandcastle/prompt.md。这只是一个写代码的惯例,不是系统自动行为。

Sandcastle不会因为你建了这个文件夹就自动去读它。你必须自己把promptFile参数配好,指向那个文件路径,系统才会去读。不配就是没读到,没读到就报错。

感叹号命令是灵魂

重点来了,这个设计非常有意思。你可以在prompt文件里面写感叹号开头的一行命令。比如写感叹号git空格log空格两个横线oneline空格横线10。系统看到这一行之后,会先去执行这条git命令,拿到最近十条提交记录的简短列表,然后把输出结果替换掉那一整行,再交给AI。这就等于你在给AI实时喂上下文,而且是活的数据,不是拍脑袋写的静态文本。

举个更狠的例子:
你再写一行感叹号gh空格issue空格list空格两个横线state空格open。gh是GitHub官方命令行工具。这条命令会去拉取当前仓库里所有还没有关闭的issue列表。AI拿到这堆数据之后,直接就能看到现在有哪些问题没解决。你都不用费劲解释需求是什么,AI自己看issue标题和描述就行了。这就相当于你把AI扔进工单系统,让它自己读单子干活。

这里有个关键点:
所有感叹号开头的命令都是并行执行的。

什么意思呢?就是你写了三条命令,系统会同时启动三个进程去跑这三条命令,不会一条一条排队跑。而且这些命令是在沙盒环境里执行的。

比如你在沙盒里改了一个文件,然后用感叹号命令去读这个文件的内容,读到的是改完之后的最新结果。这点非常关键,不然上下文会错乱。AI以为环境是A状态,命令拿回来的数据是B状态,那就全乱套了。

但有个大坑:
这个设计虽然很强,但有一个非常坑的地方。只要有一条感叹号命令执行失败了,不管是命令拼写错了还是网络断了还是权限不够,整个流程直接挂掉,不继续往下走。没有重试,没有跳过,没有任何容错机制。

​​​​​​​这就逼着你在写这些命令的时候必须非常稳。每条命令都得是可重复执行的,都得考虑好各种异常情况。你不能写一条可能失败的试探性命令,一旦失败整个任务就白干了。