Google Cloud Blog

How I learned Go in a Day with Antigravity 2.0 and How You Can Do the Same

8.5内容质量
How I learned Go in a Day with Antigravity 2.0 and How You Can Do the Same

TL;DR · AI 摘要

作者通过 Antigravity 2.0 工具快速学习并用 Go 语言重构 CLI 工具,实现零依赖、高性能、低内存占用。

核心要点

  • Go 语言提供了同步、线性代码和丰富的标准库,适合构建高性能 CLI 工具。
  • Antigravity 2.0 可以自动化代码翻译、测试生成和平台路径映射。
  • 使用 Go 重构 CLI 工具后,启动时间缩短至 2ms,内存占用仅 11MB。

结构提纲

按章节快速跳转。

  1. 作者分享了如何通过 Antigravity 2.0 工具快速学习 Go 并重构 CLI 工具。

  2. 作者在项目开始前设定了零依赖、高性能和安全性的目标。

  3. 作者研究了多种语言和工具,最终选择 Go 作为实现语言。

  4. Antigravity 2.0 自动化了代码翻译、测试生成和平台路径映射。

  5. 重构后的 CLI 工具启动时间仅 2ms,内存占用 11MB。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • Go 语言学习与 CLI 工具重构
    • 学习目标
      • 零依赖
      • 高性能
      • 安全性
    • 替代方案评估
      • Rust(性能高但复杂)
      • Python(依赖多)
      • Zig(低级控制但缺乏库)
      • Swift(跨平台差)
      • Go(平衡选择)
    • Antigravity 2.0
      • 代码翻译
      • 测试生成
      • 平台路径映射
    • 重构成果
      • 启动时间 2ms
      • 内存占用 11MB

金句 / Highlights

值得收藏与分享的关键句。

#Go#CLI#Antigravity#工具链#性能优化
打开原文

我如何用 Antigravity 2.0 一天内学会 Go,以及你也可以这样做 | Google Cloud 博客

开发者与实践者

我如何用 Antigravity 2.0 一天内学会 Go,以及你也可以这样做

2026 年 6 月 16 日

##### Alex "Sandu" Astrum

开发者关系,Antigravity

我一直在探索如何从 NPM 依赖项的负担中重新夺回我的软件栈,并用编译后的、单二进制文件的 Go CLI 替换我资源消耗大的 Node.js 运行时。我努力的成果是 skl,这是一个快速的工具,我们用它来管理代理技能,启动时间只需 2 毫秒,仅使用 11MB 的内存。

但具体来说,我是如何做到的呢?

简单来说,我设定了架构目标并审查了逻辑,而 Antigravity 则负责代码转换、测试生成和平台路径映射的机械工作。本文描述了我们迁移工作流程的逐步操作,帮助你构建自己的工具。

步骤 0:设定个人学习目标

在编写任何代码之前,你首先要定义项目的边界。在我们的情况下,我想要一个零依赖的核心,使用最少的外部包。我决定我们的 CLI 工具需要快速,我们的安全模型在适当的地方必须是零信任。在这个过程中,我的代理添加了特定的约束:对所有输入进行清理,阻止路径遍历,并对文件夹扫描施加深度限制,以防止 CPU 挂起。

我开始通过提示 Gemini 来审查替代的栈,并帮助我们权衡它们的利弊。

加载中...

在线研究并识别 3-5 个可用于替代 TypeScript 的 CLI 工具构建方案,并解释为什么(重点放在性能和安全性上),并提供具体的例子和链接。

我们考虑了一些替代方案:

  • Rust 的性能非常出色,但其借用检查器规则和生命周期注解的管理为我们的简单符号链接工具带来了太多摩擦。
  • 如果你选择 Python,你将不得不分发运行时解释器并管理虚拟环境,通过 pip 拖入打包开销,这是我们想要避免的。
  • Zig 提供了出色的底层内存控制和编译速度,但其缺少开箱即用的高级标准库抽象,用于 HTTP 操作和归档提取。
  • 编译后的 Swift 在 macOS 上提供了干净的脚本编写,但其在 Windows 和 Linux 上的跨平台编译能力不太适合我们的多平台需求。

对我们来说,Go 找到了合适的平衡点:它为我们提供了同步、线性的代码,即时编译,以及丰富的标准库。

为了确保我没有重复别人已经完成的工作,我通过直接提问启动了这个项目:

我想将 npx skills 转换为 Go。之前有人这样做过吗?

代理研究了网络并确认了 vercel-labs/skills 仓库没有官方的 Go 端口。它确认了虽然官方 CLI 是基于 TypeScript 并通过 npm 分发,但代理技能规范本身是开放且语言无关的。这意味着我们可以从头开始构建一个编译后的 Go 端口。

而且,因为我希望在这个过程中学习,我还请求了 Go 特定的技巧、窍门和陷阱:

识别 3-5 种使用 / 不使用 GO 的模式,并向我解释它们。

为了充分利用我所不熟悉的语言的最佳实践,我决定先找到最受欢迎、评价最好的 Agent Skill(指导 AI 编码助手的指令),并安装它,然后再编写任何代码,甚至在开始规划之前。首先奠定环境基础,可以确保后续编写的任何代码或规划都符合社区的共识风格。

技能搜索提示

我问代理目前有哪些适用于 Go 的社区 Agent 技能:

what are the top community agent skills for go?

一旦代理建议了 samber/cc-skills-golang,我就让它安装这个技能包:

add all skills from samber/cc-skills-golang

安装完成后,我手动验证技能是否被发现并准备就绪,通过输入 /golang- 来触发自动补全。

第二步:差距分析与规划

我通过给代理以下指令来初始化架构目标:

计划将 npx skills 的 100% 功能移植到 Go,重点是安全性、最佳实践,并实现 90% 的单元测试覆盖率。拉取仓库并进行映射。如有任何问题,请随时问我。

我们的第一个主题任务是动态的入职流程。当被问及默认值时,我建议如果没有找到代理,就提示安装 antigravity-cli。我还定义了当检测到多个活跃代理时的回退行为,即使用通用目录:

对于 MVP,我们的目标是支持 Antigravity 2 作为默认值,并通过符合标准的 '.agents' 目录回退到通用目录(如果检测到多个代理)。

实现

在我批准计划后,Antigravity 系统地将所有 51 多个代理配置记录进行了转换(尽管我没有明确要求所有这些内容,AI 正确地将任务识别为足够简单,可以直接包含在 MVP 范围内),将 Aider、Claude Code、Cursor、Zed 等从 TypeScript 映射到 Go,确保我们全面覆盖所有环境。

核心结构方便地位于一个文件 types.go 中:

type AgentType string type AgentConfig struct { Name string DisplayName string SkillsDir string GlobalSkillsDir string ShowInUniversalList bool DetectInstalled func(home, configHome, cwd string) bool } ...

这种映射效果很好。例如,Zed 的检测逻辑仅用几行代码就动态处理了 Linux(Flatpak)、macOS 和 Windows 配置:

"zed": { Name: "zed", DisplayName: "Zed", SkillsDir: ".agents/skills", GlobalSkillsDir: filepath.Join(home, ".agents/skills"), DetectInstalled: func(h, c, w string) bool { return exists(filepath.Join(c, "zed")) || (zedAppDataHome != "" && exists(filepath.Join(zedAppDataHome, "Zed"))) || (zedFlatpakConfigHome != "" && exists(filepath.Join(zedFlatpakConfigHome, "zed"))) }, }

接下来,我注意到 Antigravity 的用户入职代码与自动映射代码混在一起。像这样的默认值是用户的个人选择,更适合在自己的文件中进行隔离:agy-onboarding.go:

move default Antigravity 2 prompting to agy-onboarding.go

在版本零搭建完成后,是时候进行测试了。

第三步:强制执行质量保证(QA)循环

为了确保 Go 移植版的行为与原始 TypeScript CLI 完全一致,我们采用了测试驱动开发(TDD)循环。我通过以下提示启动了这个过程:

应用 TDD 原则和 https://preslav.me/2026/05/19/10-golang-error-handling-commandments/

这启动了 TDD(测试驱动开发)流程。我没有明确提示代理使用技能,而是引导它获取第三方最佳实践博客文章,这提醒了代理相关的 Agent 技能(golang-how-to、golang-testing、golang-error-handling 和 golang-cli)。由于 Antigravity 有沙箱环境,它解析了这些技能并自动开始执行 QA 循环。每当它即将更改功能代码时,都会继续在当前路径中重新应用这些 TDD 原则。

优先测试的 frontmatter 解析

对于 frontmatter 解析,代理首先使用 Go 的表格驱动测试模式(这是我第一次发现的新模式,非常令人愉快)编写了 frontmatter_test.go:

go
func TestParseFrontmatter(t *testing.T) {
    tests := []struct {
        name     string
        raw      string
        wantData map[string]interface{}
        wantContent string
    }{
        {
            name: "valid frontmatter",
            raw: "---\nname: my-skill\n---\n# Content\n",
            wantData: map[string]interface{}{"name": "my-skill"},
            wantContent: "# Content\n",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            gotData, gotContent, err := ParseFrontmatter(tt.raw)
            // 断言结果...
        })
    }
}

当 Antigravity 运行 go test 时,正如我们所预期的那样,测试失败了。随后,我的代理生成了 frontmatter.go,实现了将文档分割并反序列化其 YAML 元数据的线性字符串扫描循环。通过使用简单的线性扫描而不是复杂的正则表达式,我们增强了工具的安全性,避免了可能使应用程序崩溃的正则表达式拒绝服务(ReDoS)漏洞。在初始提示中将“安全性”作为目标,即使原始 Node 实现使用了正则表达式,也使代码更加安全。

通过错误准则进行对齐

既然我们正在讨论错误处理,我将在这里介绍我们如何将错误结构与 Preslav Rachev 的《10 条 Golang 错误处理准则》对齐。Go 要求你显式返回错误值,而不是像异常那样捕获它们。通过整合这些规则,我引导代理在每一层都立即检查错误(if err != nil),并在将其传递到调用栈之前,用上下文信息对其进行包装(fmt.Errorf("action: %w", err))。在对生成的代码进行最终审查时,我意识到 Antigravity 忘记了这一最佳实践,因此我提醒它:

在所有文件中缩短错误信息,删除“failed to”前缀等。请参阅《10 条 golang 准则》

它迅速在整个代码库中修复了这些问题。

单元测试是否足够?

简短的回答是:不够

为了确保 AI 在翻译过程中没有引入细微的错误或幻觉,我进行了代码审查,而不是盲目信任通过的测试套件。

当我审查生成的测试时,我意识到仅通过绿色检查是不够的:我们缺少了针对那长长的安装位置列表以及各种情况的测试,例如没有代理、只有一个代理或多个代理同时处于活动状态。由于这是一个完整的重写,我希望为这些流程提供端到端的集成测试覆盖。为了解决这一差距,我向 Antigravity 提供了一组有针对性的场景:

添加集成测试:

  1. 没有安装代理:验证它是否安装到 antigravity 并输出 agy-cli 的入门提示。
  2. 支持所有代理,但其中一个不支持。
  3. 恰好安装了一个代理,包括同一路径可能被多个代理归属的情况。
  4. 支持非参数化代理。

注意:像 Claude Code 或 Codex 这样的非参数化代理在加载包时(或通过环境变量)会全局定义其配置路径,而不是在运行时扫描当前活动的工作区文件夹。

添加这些测试的更改列表没有触碰任何生产文件,逻辑是稳固的。但我不想将此交给运气。如果你关心某个特定功能或工作流程,就必须明确表达。花五分钟验证你的端到端覆盖率并定义几个稳固的测试,可以保护用户免于日后遇到损坏的发布版本。

第 4 步:用于 CLI 命令的并行子代理

当你将一整套 CLI 命令(init、add、list、remove、find、update 等)及其子选项移植时,你将面临一个较大的表面区域。与其按顺序迁移它们,不如并行化我们的工作。在我们的情况下,这是一个不错的选择,因为我们希望每个子代理专注于特定主题,而不是记住整个工具,这帮助我们发现了一些漏洞。

然而,子代理并不总是最佳选择;你只应在大量、独立且界限明确的任务上优先考虑并行执行。当正确执行时,并行子代理消耗的标记不会显著多于一个长时间运行的线程,但它们可以保护主协调代理在大型代码库的重量下不触及上下文压缩限制。大多数简单的项目并不需要这种级别的规模。一个良好的经验法则是将子代理保留用于相当于数十个功能和数十个子功能的工作负载。

在之前的步骤中,我运行了一个单一代理,以快速且高效地构建一个最小可行产品。但我不确定它是否完全移植了代码。所以我直接询问它:

你是否覆盖了原始 CLI 的 100%?子代理是否分别研究每个选项和每个测试并填补空白?

结果证明这是一个正确的决定。子代理对命令进行了深入的审计,发现了几个选项缺口和缺失的测试,这些随后在此次审计提交中被整合。

每个子代理只处理一个命令。它们分析了标志排列,如 -g/--global 和 --copy,起草了基于表格的单元测试,并验证了它们的代码是否干净地编译。一旦它们报告回来,主协调器会整合它们的更改,解决任何冲突,并验证整个合并后的项目是否成功编译。

大象和金鱼

为了在此次迁移过程中保持代理的专注,我们使用了“大象和金鱼”的隐喻,这是一个在 Google Research 的《大象、金鱼和软件工程的新黄金时代》中记录的架构模式。这依赖于两个不同的角色:大象(长期协调会话,持有设计规则和代码库记忆)和金鱼(短暂、干净的子代理,你生成它们来执行单个任务,没有背景历史)。

虽然 Antigravity 确实使用了自动化的会话压缩来管理其上下文大小,但你可能希望主动管理自己的上下文窗口,通过维护自己的清单并将工作划分为孤立的、短暂的子代理,以实现“少即是多”的清晰度。

第 5 步:包结构、编译和 CI/CD

通过一些来回的沟通,我了解到 Go 包的结构方式,并识别出需要考虑的限制。现在我有了一个结构清晰、文档完善的 package main.go,支持原生安装:

go
go install github.com/alexastrum/skl@latest

我提示代理捕获实现细节并将其记录下来,供以后参考:

go
summarize findings for humans in README.md, considerations for agents in AGENTS.md

为了验证构建过程、自动运行测试并确保它在其他机器上也能正常工作,我要求代理:

go
make sure it builds on all supported platforms

Antigravity 设置了 ci.yml 工作流来运行矩阵构建,这有一个令人惊讶的依赖项:

go
env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" # HMMMMMM ??? jobs: test: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] # ...

意想不到的注意事项

  • 荒谬的是,尽管我们从 Node 迁移到了 Go,但 GitHub 管道仍然依赖 Node 来使用标准的 GitHub Actions 工具,如 actions/checkout 和 actions/setup-go。
  • 该工具已经完全准备好在本地运行和编译。然而,如果我们想要向其他用户分发预编译的二进制文件,我们需要为 macOS 和 Windows 配置代码签名。

由于使用代码签名构建自定义操作是一个复杂的过程,因此最好留到以后再进行。

第 6 步:创建代理技能

是时候记录这个过程本身了。为了将此工作流程制度化,我们创建了一个可重复使用的代理技能。

我首先要求代理规划一个技能创建提示,其中包括最重要的步骤:

go
Review the current trajectory (including my specific prompts that generated accepted results) and lets plan to create a `/cli-to-go-migration` skill. What steps should the skill follow?

我得到了一个草稿提示,并对其进行了迭代。经过一些来回的讨论后,我最终的指令基于五个核心规则(尽管你的可能不同)。这是我最终使用的提示:

回顾当前的轨迹(包括生成被接受结果的特定提示),让我们计划创建一个 /cli-to-go-migration 技能。规则:#### 1. 目标 在提出代码之前,代理必须首先进行研究。它识别更广泛的用户目标,审查多种技术栈的替代方案,并检查已有工作,以锁定一个目标语言并研究其惯用法。#### 2. 设置 在修改任何文件之前,代理会验证或初始化一个 Git 仓库,以保持清晰的历史记录。之后,它还必须直接报告下载失败,并在所有独立工作完成后优雅地失败,而不是回退到占位符或非终止循环。#### 3. 导入现有知识 如果需要的基础技能(如 golang-cligolang-testing)缺失,但在提示中明确命名,代理将阻止执行,并在请求确认后提供自动安装它们的选项,而不是打印开发者需要遵循的说明。#### 4. 断点 该技能为已知的 AI 痛点设置了硬性停止点。当代理遇到特定问题或出现混淆时,它会暂停,以供人工或算法验证。#### 5. 对齐检查 每当我们看到对齐偏差的迹象时,我们需要设定明确的规则。例如,当我注意到代理过度编辑了一些文档而遗漏了其他文档时,我设定了一个规则,即代理应只将 /humanizer 技能应用于面向用户的文件,如 README.md 或帮助文档,而保留结构化的开发者上下文(如 AGENTS.md)不受样式编辑的影响,以确保其他代理可以准确解析其元数据。

没有一种放之四海而皆准的方法,但要求代理创建一个技能并将其锚定在几个防护栏上是一个良好的开端。在实践中,你可能会轮流打磨多个提示,直到你感觉代理的响应与你的目标一致。然后你会要求 AI 进行校对,最后对 SKILL.md 的内容进行人工审查。

结论

用 Go 语言重新构建 skl 是一次有趣且富有教育意义的经历,解决了我个人的工具需求。它有效运行了,所以我决定记录这个过程。通过这个视角的思考,我意识到旅程本身才是最大的回报。通过将你的架构选择编码成可重用的技能和个人经验,你作为工程师会不断成长;而编译后的二进制文件则是你流程有效的物理证明。

令人惊讶的是,这次迁移过程中我经历的最显著变化是行为上的。

摆脱集成开发环境(IDE)并使用 Antigravity 2.0 让我更容易保持宏观视角,避免在迁移过程中深入修复出现的问题。相反,它引导我理解问题发生的原因,并学习 Go 语言特定的细节。

在传统的 IDE 中,当你的助手遇到问题时,你的本能反应是拿起键盘进行调试。在没有编辑器的情况下工作,迫使你保持架构师的角色,从导航甲板上引导机器,而不是亲自进入引擎室灭火。这正是我们学习如何在大规模上管理代理的方式。

发布于

  • 开发者与实践者