昨天我用 Claude Code 写了一条 commit message。diff 是一行修改:一个注释里的拼写错误。Claude Opus 读取了 diff,思考了两秒钟,生成了 fix: correct typo in auth comment。为此,它用了大约 800 个输入 token 和 30 个输出 token,分别花费 $15 和 $75 每百万 token。总成本:不到一分钱。但是,把这个过程放大到一天 40 次 commits,250 个工作日,一家公司有 200 名开发者使用 coding agents,那么这些看似微不足道的花费会积累成数千美元的开销,付出的只是等同于贴创可贴的智力劳动。

问题并不是 Opus 太贵。问题在于 coding agents 不会区分 $0.001 的任务和 $0.10 的任务。一切都通过同样的大模型执行。生成 commit 信息、分类一个 issue、验证格式 —— 全部使用高成本的模型,就像雇用一位外科医生来贴创可贴。

数据与成本

我们用 Claude Opus 4(上一代,目前大部分生产环境还在用的版本)的价格来算一个账:

任务输入 Token 数输出 Token 数成本
生成 commit 信息(小型 diff)~800~30$0.014
分类某个 issue~500~50$0.011
验证 commit 格式~300~20$0.006
生成 standup 报告~2000~200$0.045

这些任务都不需要一个拥有 2 万亿参数和多步推理能力的大模型。这些仅仅是带有强约束的分类和生成任务。本质上就像是根据颜色对卡片进行分类。

而使用苹果 Apple Intelligence 的 on-device 模型(3B 参数,内置于 macOS 26):成本 $0.00,延迟约 300ms,无需网络,无需 API 密钥。

foundation-hooks

foundation-hooks 是一套 Swift 编写的四个二进制文件,利用苹果的 Foundation Models 框架自动化开发过程中不需要耗费云端模型资源的任务:

二进制功能Git Hook
fm-commit-msg根据 diff 自动生成常规 commit 信息prepare-commit-msg
fm-validate-msg验证 commit 信息格式,提供修正建议commit-msg
fm-lql-create分类并通过 lql 在 Linear 创建 issueCLI
fm-lql-standup根据 git log + issues 生成 standup 总结CLI

这四个工具都遵循同一个模式:定义一个带有 @Generable 注解的 Swift struct,传递最低限度的上下文给模型,只需几毫秒就能生成结构化输出。

安装步骤:

git clone https://github.com/frr149/foundation-hooks
cd foundation-hooks
make build && make install-hooks REPO=/path/to/your/repo

从这之后,每一次 git commit 都会自动生成一个符合规范的信息。这些 hooks 已经在 11 个生产仓库中安装,并测试了两周。

原理:@Generable 与 constrained decoding

下面是技术亮点所在。@Generable 并不是「在 prompt 中要求模型返回 JSON 然后祈祷它正常工作」。它是 constrained decoding —— 模型从字面上无法生成违反 schema 的 tokens。

工作机制

  1. @Generable 是一个 Swift 宏,能在编译时从 struct 自动生成 JSON Schema。
  2. 框架将 schema 注入到 prompt 中,指定响应的格式。
  3. 在推理过程中,每一步解码都应用 token masking:根据 schema,将所有无效的 tokens 从词汇表中屏蔽(在 softmax 中概率设为 0)。
  4. 模型只能选择有效的 tokens。

苹果在 WWDC25 文档 中将其描述为「guided generation」。这是 OpenAI 的 response_format: json_schema 和 Anthropic 用于工具使用的方式相同的技术。不同之处在于:苹果将其集成至 Swift 的类型系统中。定义 struct,编译器生成 schema,运行时在推理中应用。这是端到端的类型安全。

三种约束级别

@Generable
struct CommitMessage {
    // 第一级:严格限制 —— 实际上是枚举
    // 激活 token masking:只有 "fix", "feat", "refactor" 等有效。
    // 构成 "bug" 或 "update" 的 token 概率为 0。
    @Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
    var type: String

    // 第二级:软限制 —— 类似于 system prompt
    // 模型倾向于遵循但不强制。
    @Guide(description: "修改的范围,例如 auth, ui, db。输入单词,使用小写。")
    var scope: String

    // 第三级:无约束 —— 自由文本,由模型决定
    var subject: String
}

类比:anyOf 就像一个下拉菜单,description 像带有占位符的输入框,一个没有 Guide 的字段则像一个空白的多行文本框。三者的区别不在于程度,而在于机制。第一种在 token 水平上操作(模型无法脱离限制),第二种在 prompt 水平上操作(模型倾向于遵循),第三种则完全自由。

这在 hooks 的使用场景中尤为重要,因为严格限制的用途非常明确。commit type 必须是 7 个值之一。没有歧义、创新或推理,仅是纯粹的分类任务。3B 参数的模型配合 constrained decoding 的表现与 200B 参数的模型一样好。区别在于前者执行需要 300ms 并且免费,而后者需要两秒且花费金钱。

一个完整 hook 的代码

以下是 fm-commit-msgprepare-commit-msg hook。它只有 106 行 Swift 代码,无任何外部依赖:

import Foundation
import FoundationModels

@Generable
struct CommitMessage {
    @Guide(description: "Type of change")
    @Guide(.anyOf(["fix", "feat", "refactor", "test", "docs", "chore", "style"]))
    var type: String

    @Guide(description: "Scope of the change, e.g. auth, ui, db, api. One word, lowercase.")
    var scope: String

    @Guide(description: "Imperative summary of the change, max 50 chars, lowercase, no period")
    var subject: String
}

guard SystemLanguageModel.default.isAvailable else {
    exit(0) // 无 Apple Intelligence:静默退出,用户直接手写
}

安装

# 前置条件:macOS 26,Xcode 26,启用 Apple Intelligence
git clone https://github.com/frr149/foundation-hooks
cd foundation-hooks
make build

# 给指定 repo 安装 hooks
make install-hooks REPO=/path/to/your/repo

# 将 CLI 可执行文件安装在 ~/.local/bin
make install-lql