使用 ZCode 从零编写 Infinity CI

介绍

9 天、90 个提交:我用 ZCode 从零写出一个 CI 服务器

全程只用了 Z Code 一款 AI 编程工具。第一天开工约 2 小时,一个基本可用的 CI 就跑起来了;9 天后,它在我的 Windows 打包机上稳定执行 Unity 6 打包。

本文主要使用 Z Code 加 GLM 5.3 Flash 编写,我来补充一些细节和背景内容

为什么要自己写一个 CI

Jenkins 从搭建起已经用了10年,在这个过程中,Jenkins 有很多问题。

比如,最开始的版本不够现代化,打包页面非常丑。后来终于增加了 Blue Ocean,但它和原来的打包界面实际上是两套东西同时并存,没有办法很方便地使用。

其次,整个 Jenkins 实际上有很多 bug。它的历史架构实在太久了,并不适配游戏的打包,特别是 Unity 游戏的打包

  1. 无法通过版本控制将项目目录更新到指定状态,因此只能编写一个单独的脚本放在第一步来模拟这个行为
  2. 同一台机器开启多个不同任务的页,某些情况下会自动刷新且会耗时很久(超过10秒)才正常
  3. 日志界面中选中复制文字会每一行会多一个空换行
  4. 想要在每条日志前增加时间戳,只能使用 BuildTime 这个插件,但这个插件无法修改时区,只有默认时区 UTC-0

游戏领域一般都用 Jenkins,因为所有人都对它很熟悉

  1. GitHub Actions。以及一些开源的实现,如 NAct 之类,其实并不方便私有部署。

  2. Team City 看起来比较合适,但最大的问题是收费软件,也就是说,同时只能有 3 个 Agent 并行运行。

  3. GitLab CI 最大的问题是和 GitLab 融合得太紧密,无法单独创建一个任务来关联多个仓库或类似操作。每个仓库只能有一条固定的流水线,使用起来看起来不太方便。

  4. Circle CI、Drone CI,或者类似的新一代 CI 软件,它们最大的问题都是针对 Linux 或容器环境提供支持,也就是说,它们并不是针对游戏领域做的专用 CI 软件,所以并不是很好用。

现阶段的目标并不是说做出来之后立刻替代 Jenkins,而是想在自己的平时业余开发中负责打包任务。随着时间推移,慢慢完善它的功能,直到它能完全替代 Jenkins,这个时候才考虑推广到其他地方使用

我想要的其实很简单:一个简化版的 Jenkins,有个 Web 界面能看到构建日志,能分发任务到装了 Unity 的打包机,微信里能收到构建结果通知。这个需求看似简单,但把 Jenkins/GitHub Actions 的核心体验(并行任务、依赖图、实时日志、分布式 Agent)都做齐,传统认知里没有几人月下不来。

看到新闻 zhipu 决定在 Zcode 中使用 GLM5.3 Flash 免费

于是 9 月 9 日晚上 11 点 40 分,我打开了 Z Code,发了一条消息:

现在使用 C# .NET 10 做一个类似 Jenkins 的 CI 服务器软件,应该做哪些技术选型?前端后端都算上

这篇文章记录的,就是接下来 9 天里,我如何用 Z Code 这一款 AI 工具,从这一条消息写到 Unity 6 打包 Windows 全流程跑通。

先看结果:Infinity CI 是什么

先介绍下这个软件本身。它叫 Infinity CI,一个用 C# / .NET 10 构建的类 Jenkins / GitHub Actions 持续集成服务器。最终数据:9 天、90 个提交、净增 22,212 行代码,全部由 Z Code 编写,我一个字一个字敲的代码行数为零。

核心能力清单:

  • 并行 Run / Job / StepGitHub Actions 式的运行模型,needs 声明依赖形成 DAG,依赖失败下游自动跳过,Run 页面渲染可点击的依赖图(Jenkins Blue Ocean 风格)
  • 分布式 Agent:Agent 主动外连 Master(gRPC),拉取式领任务、心跳租约、断线自动重连、孤儿任务自动重排队——打包机不需要开任何入站端口,NAT / 防火墙友好
  • 全页面实时:每个页面一条独立 SignalR 连接,数据变化实时上屏,整个项目没有一个手动刷新按钮;心跳看门狗指数回退重连
  • xterm.js 彩色日志:逐行时间戳、JSONL 断点续传、按 Step 下载、ANSI 全彩渲染
  • 触发方式:Web 手动 / Git push webhook(HMAC 验签 + 分支通配过滤)/ PR 触发 / cron 定时
  • 配置即 Git 仓库:每个任务的 YAML 配置本身是一个 Git 仓库,Web 表单保存即自动 commit,天然获得历史、回滚、克隆
  • 企业级补全:RBAC 三级角色 + 项目可见性 + 个人 API Token + LDAP 域登录
  • 通知:企业微信 / 钉钉 / Slack / 通用 Webhook / 邮件,按事件过滤
  • 部署:SQLite 单文件存储,可发布为 win-x64 自包含单文件 EXE,也有多阶段 Dockerfile

技术栈一览:

技术
服务端 C# / .NET 10、EF Core + SQLite、SignalR、gRPC(Agent 通道)、Serilog、LDAP
前端 React 19、TypeScript、Rsbuild(Rspack)、Tailwind CSS v4、xterm.js 6、i18next 中英双语
Agent .NET Worker + gRPC 双向流
协议 Protobuf 定义 Agent 协议、YAML 定义工作流

架构一句话:Server(调度 + Web)→ Agent(装在打包机上领任务干活)→ Web UI(实时看板),外部对接 Git 仓库和通知渠道。

📷 截图位:Infinity CI 首页 Dashboard(Jenkins 风格任务卡片)

第一天:约 2 小时,从一条消息到能用的 CI

9 月 9 日 23:40,我发出第一条消息(就是上面那条问技术选型的)。Z Code 给出了完整的技术选型分析,我补充了一条关键需求:

打开的每一个网页都作为一个单独的客户端实时与服务器连接,能拿到最新的状态。 用户可能在一台机器上打开多个网页,分别查看不同任务,互相之间不能有影响。

然后就是见证效率的时刻了。看当天的 git log:

1 2 3 4 5 6 7 8 9 
00:22 chore: scaffold .NET 10 solution (Core/Server/Agent/Tests) 00:26 feat(core): job 定义模型、YAML 解析校验、跨平台 shell 解析器 01:01 feat(server): 构建队列、进程执行引擎、基于偏移量的日志存储 01:15 feat(server): SignalR hub(按连接分组订阅 + offset 续传) 01:19 feat(server): jobs/builds REST API + 分页日志 01:26 feat(web): Rsbuild + React 19 + Tailwind v4 前端脚手架 01:37 feat(web): 实时仪表盘 + xterm.js 日志流详情页 01:52 feat(agent): gRPC Agent 协议(拉取式派发 + 租约恢复) 02:01 feat(web): Agent 管理页;gRPC 独立 HTTP/2 端口 5001 

从 23:40 的第一条消息到凌晨 2:01 最后一个提交,大约 2 小时 20 分钟,一个 CI 服务器需要的全部骨架——.NET 10 后端(任务队列、进程执行引擎、SignalR 实时推送、REST API)、React 19 前端(仪表盘 + 日志流)、gRPC 分布式 Agent——三线全部打通,而且每一块都有提交、有测试项目。这中间我几乎没有再打字,Z Code 在持续自主施工。

到这里还没完。第二天早上 7 点 57 分,我睡醒后发了一条大需求:

UI 风格改为 https://github.com/actions/runner-images/actions/runs/34350318609/job/102461706314 需要支持并行任务 每一步改为单独的 Console,像上面链接中的一样 每一条日志需要增加时间戳 需要增加任务配置界面 需要增加用户权限系统,支持超级管理员、普通管理员、普通用户,支持设置每个用户可见的项目 ……

半小时后,git 历史上出现了两记 breaking change 重构:一个 52 文件 +2972/-1959 行,把执行模型改成了 GitHub Actions 式的并行 Run;一个 20 文件 +2190/-522 行,前端整体换成 GHA 风格 UI + 登录页 + 角色权限。第一天结束时,它已经不是一个玩具,而是一个有权限体系的、对标 GitHub Actions 运行模型的 CI 了。

这里有个我事后复盘很感慨的点:如果是人写代码,“推翻执行模型重写"这种事,第一天的 psyche 扛不住。但 Z Code 重构 3000 行跟写 300 行一样平静,我只需要决策"要不要这么改”。

第一天就发现,GLM 5.3 Flash 这个模型已经给出了一个初步可用的版本,完全没有用人去介入,感觉非常好

第二天:功能大爆炸(23 个提交)

9 月 11 日是整个项目提交最多的一天,23 个提交。凌晨 3 点 21 分我发了一条消息,一口气提了四个需求:

增加运行服务器与 Agent 脚本 job 可以指定依赖的 job,从而形成一个图 需要将 job 由依赖关系组成的图显示出来,并且可以点击后切换下面的 job 详情及 steps 日志,使用 GitHub 风格 每个任务的配置放到单独的 Git 仓库中,并且可以查看历史,也支持克隆到本地

这一天落地的东西,随便拎一个出来都是正经 CI 的核心功能:

  • DAG 依赖调度needs: [a, b] 声明依赖,失败级联跳过,页面上渲染成可点击的依赖图;
  • 配置即 Git 仓库:任务的 YAML 存成独立 Git 仓库,Web 表单保存即 commit,能看历史、能回滚、能 git clone——这个设计我很得意,比 Jenkins 把配置埋进数据库优雅多了;
  • Git SCM 集成:工作流声明 scm: 块,构建前自动检出源码;
  • Jenkins 风格首页 Dashboard:任务卡片、状态图标、收藏置顶;
  • Webhook 触发 + 企业微信机器人通知

最有意思的是 DAG 图的视觉打磨。我拿着 Jenkins Blue Ocean 的截图逐条提意见:

学习这张图片中的任务图风格,开始和结束以及大部分结点在一条线上,并行的任务按照顺序排在并行结点下方。结点之间使用圆角直线连接。

ok 的进入线与退出线的后半段变成了直线,需要改成圆角肘形

一个上午连修五轮,最终的效果是:主线结点在一条水平线上、并行分支垂下来再汇聚、圆角肘形连线、成功打勾失败打叉。这就是 AI 编程的正确用法之一——审美和"像不像那个产品"的判断我来给,SVG 连线怎么画让它去迭代

📷 截图位:Run 页面的 Blue Ocean 风格 DAG 依赖图

在换成 Jenkins Blue Ocean 风格的任务依赖图之后,觉得这个模型的能力可以满足开发的需求。因此充值了付费套餐以解锁在晚上11点到第二天早晨9点之间免费使用 GLM 5.3 Flash 的权限,继续开发

模型给我最大的感受是,它可以通过浏览器来截图,获取当前的状态。而且可能是内置浏览器有 MCP,也能获取当前浏览器内部的一些状态

简单说,就是模型自己可以实现编写功能并验证这个循环

第 3 到 8 天:每天一个主题

之后的一周,节奏变成"每天一个主题",每天睡前或起床后看一眼哪里不顺眼,丢给 Z Code:

9 月 13 日(企业化):从失败步骤重试、复制任务、i18next 中英双语、LDAP 域账号登录。到这一步,拿去公司内网部署已经没有障碍了。

9 月 14 日(管理体验):暗色主题、Projects 页面、真实的队列页(显示 Agent 派发等待状态)、Agent 配置界面(并发 / 标签 / 环境变量)。

9 月 15 日(数据模型大迁移):我发了一条带 checkbox 的长消息:

  • 运行不是全局的,应该跟着任务走,且 ID 只在任务内自增,不是全局自增
    • 现在地址是 http://127.0.0.1:5000/runs/5,应该为 http://127.0.0.1:5000/runs/dag-demo/5
  • 配置需要放到任务单独子目录中进行版本控制
  • 日志也需要放到任务单独子目录按运行 ID 进行存放
  • 简单说就是不再先类别后任务,而是先任务后类别

Z Code 完成了"任务优先"目录布局的整体迁移:每个任务独立目录、独立仓库、任务内自增编号、/runs/{任务}/{编号} 式 URL。这种牵一发动全身的改造,AI 做起来反而干净。

这几天尝试使用了 ZCode 移动端远程控制,也就是用手机访问网页来控制 AI 干活儿。使用这个功能,要求电脑必须一直开机

9 月 16 日(对标审计日):这天的起点是一条灵魂拷问:

现在全盘检查一下,Jenkins 该有的功能都实现了吗?还差哪些关键与必需的?

Z Code 做了全盘审计,列缺失清单,我排了个优先级,然后这一天产出了两个里程碑提交:一个是全页面实时化改造——架构改为每个页面独立 SignalR 连接 + 心跳看门狗(15s ping、指数退避、连挂 5 次自动刷新页面)+ 删掉所有刷新按钮,26 文件 +2567/-1725;另一个是六项功能补齐——cron 定时触发、用户 API Token、SCM webhook HMAC-SHA256 验签、Job/Step 超时 kill 进程树、if: always() + Step 重试、越权过滤修复,最后一句提交信息写着"104 个测试全绿"。

9 月 17 日(生态集成 + 决战 Unity,下面单独讲)

9 月 18 日(收官打磨):凌晨围绕着 xterm.js 终端的"最后一公里"连修七个提交——隐藏自绘滚动条、拦截滚轮、焦点管理、Ctrl+C 复制(还专门兼容了某个 Chrome 复制扩展劫持选区的行为)。晚上以 LDAP 管理界面(配置存库热生效 + 连接测试诊断 + 组映射)收尾,也就是今天的 HEAD。

每日提交分布:13 / 23 / 0(休息)/ 5 / 6 / 7 / 10 / 17 / 9。是的,9 月 12 日一个提交都没有——写 Side project 别硬撑,休息一天效率更高。

决战 9·17:Unity 6 打包 Windows 跑通

前面八天,Infinity CI 已经是个"完整的软件"了,但对我而言它还缺终极验证:它能不能真的把 Unity 6 的包打出来?

我特意设计了解耦的集成方式:CI 里没有一行 Unity 专属代码。Unity 的构建知识(怎么调命令行、输出到哪)全部留在游戏仓库自己的脚本里,CI 只负责调度。9 月 17 日早上 8 点,在 Unity 工程里我用了三条消息让 Z Code 写完整个打包链路:

Client/Assets/Editor/Builder.cs 直接在其中增加一个 MenuItem 打包 Windows 的方法

CI/platform/windows/build.py 调用这个 Builder.BuildWindows 方法

CI/platform/windows/prepare.py 打印所有环境变量

11 分钟,三个文件写完。Unity 侧是一个标准的命令行构建入口:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 
public static class Builder {  [MenuItem("Tools/Build Windows")] public static void BuildWindows() { var scenes = EditorBuildSettings.scenes .Where(s => s.enabled).Select(s => s.path).ToArray(); BuildPlayerOptions options = new BuildPlayerOptions { scenes = scenes, locationPathName = Path.Combine("Builds/Windows", "UnityCIGame.exe"), target = BuildTarget.StandaloneWindows64, }; BuildPipeline.BuildPlayer(options); } } 

Python 侧的 build.py 定位 Unity 6(6000.0.23f1)的安装路径,拉起命令行:

1 2 3 4 5 6 7 
args = [ str(unity_exe), "-batchmode", "-quit", "-nographics", "-projectPath", str(CLIENT_DIR), "-executeMethod", "Builder.BuildWindows", "-logFile", "-", # 日志走 stdout ] 

然后 CI 侧,任务配置就只需要这么几行 YAML:

 1  2  3  4  5  6  7  8  9 10 11 12 
name: UnityCIGame project: Default jobs:  build:  runs_on: agent  # 分发到装了 Unity 的 Windows 打包机  steps:  - name: prepare  command: python CI/platform/windows/prepare.py  - name: build  command: python CI/platform/windows/build.py  - name: deploy  command: python CI/platform/windows/deploy.py 

当然,真实世界没有这么顺利。第一次把 Unity 任务真正跑起来就撞了墙:Agent 默认把源码检出到自己的 workspace,但 Unity 工程根本不在 Git 源里,脚本路径全错。我把需求丢给 Z Code:任务要能直接绑定机器上的本地目录。功能上线后第一次实测仍有路径问题,我直接把报错日志贴回去:

python: can’t open file ‘…/agent-data/workspaces/21-build/CI/platform/windows/prepare.py’: No such file or directory 路径不对,用的应该是 C:/Users/NETWORM/Work/Projects/20260917-UnityCIGame/CI/platform/windows/prepare.py

半小时内两轮修复,“任务本地目录”功能成型:任务可以直接指向本地路径,Agent 就地执行。这个功能反过来让所有 CI 产品都头疼的“Unity 大工程如何进 CI”变成了伪命题——工程不动,CI 来就你

早上 9 点 01 分,正式产物 Builds/Windows/UnityCIGame.exe(约 95 MB)落地——从 Builder.cs 的第一行代码,到 CI 产出 Windows 包,全程约 1 小时。紧接着我又提了一条:

build.py 增加实时日志输出,每一行日志都要 flush

于是 Unity 的海量编译日志通过 -logFile - 流式输出,经 Agent 的 gRPC 逐行回传,在 Web 端的 xterm.js 终端里实时滚动、带颜色、带时间戳;Run 结束的瞬间,企业微信里已经收到带状态颜色的构建卡片。9 点 06 分收尾提交,此后这条流水线每天都在跑——第二天的 shader 编译日志还证明它凌晨还在干活。

📷 截图位:Unity 打包任务的 Run 详情页——DAG 图 + xterm.js 实时日志

回头看这个集成哲学:Infinity CI 不认识 Unity,未来要接 Godot、Unreal,也只是游戏仓库里换几行脚本的事。CI 引擎专注做好调度、日志、分发、通知这些它该做的事。

总结

使用 Zcode 已累计使用7.4亿词元 其中95%以上是用来开发这个软件的

用 Z Code 写整个软件的九条体会

9 天下来,对"怎么用 AI 写一个完整软件"这件事,我有一些具体的体感:

1. 第一条消息决定坡度。 我没有一上来就说"给我写个 CI",而是先问技术选型,再补充架构级的要求(每个网页独立实时连接)。AI 给出的选型(.NET 10 + React 19 + gRPC + SignalR)我基本照单全收,事后证明全是对的。

2. 小步提交是最好的护栏。 90 个提交几乎全是 Conventional Commits 规范的原子提交,这是 Z Code 自带的习惯。每次出问题,我直接把 URL 和日志贴回去(“http://127.0.0.1:5000/runs/3 报错”),它自己定位自己修。git 历史就是调试时的地图。

3. 用真实产品的截图和链接驱动 UI。 “UI 风格改为这个链接”、“学习这张图片中的任务图风格”——比任何文字描述都精准。第二轮开始,AI 交付的还原度高得惊人。

4. 放心让 AI 推倒重来。 第一天上午两次 breaking 重构(合计改 72 个文件),第五天整体数据布局迁移。在 AI 编程里,“重写"的成本曲线被拉平了,架构决策不该再为沉没成本妥协。

5. 灵魂拷问要人来做。 “Jenkins 该有的功能都实现了吗?"——这一条消息引发的全盘审计比我自己想需求高效十倍。人负责提出好问题、排优先级,AI 负责穷举和实现。

6. 密集反馈循环是质量的关键。 9 月 18 日凌晨修 xterm 体验,我的消息是这样的节奏:“还是存在滚动条” → “依然有” → “已经强制刷新了,你可以自己强制刷新测试一下”。经常几分钟就是一轮反馈。AI 不怕你挑剔,怕的是你不验证。

7. 一款工具全栈通吃。 这个项目横跨 C#、TypeScript/React、Python、Protobuf、YAML、Dockerfile、LDAP、SQL——全部出自 Z Code 一款工具。不需要"前端 AI、后端 AI"各来一个。

8. 测试从第一天就跟着走。 Core 和 Server 都有测试项目,到 9 月 16 日已经有 104 个测试。每次大重构后"测试全绿"是敢单击提交的底气。

9. 人类的时间花在刀刃上。 9 天里我真正投入的,是每天几十条消息的产品决策和验收反馈。Z Code 把"实现"这件事的成本降到了"说清楚要什么”。

结语

最终交付物:

  • Infinity CI:9 天、90 个提交、22,212 行净增代码,类 Jenkins / GitHub Actions 的自托管 CI,单 EXE 或 Docker 部署,开源在 github.com/networm/InfinityCI
  • 一条真实在用的流水线:Git 事件 / 手动 / cron 触发 → Server 调度 → Windows 打包机上的 Agent 拉起 Unity 6 命令行打包 → 实时彩色日志回传 → 企业微信收通知

回到开头的问题:个人开发者到底值不值得自己写一个 CI?放在以前,我的答案是不值得。但现在,一个合理的技术选型 + 一款足够强的 AI 编程工具 + 每天下班后零散的反馈时间,9 天就能得到一个完全长在自己需求上的软件——不用伺候插件,不用迁就云服务,打包机插上网线就能加入集群。

这是我用 Z Code 写的第一个完整软件,大概率不是最后一个。

版权声明:
作者:lichengxin
链接:https://www.techfm.club/p/238553.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>