自建一套 vs 直接用现成的:把维护成本摊开算一遍
【免费下载链接】bamaBAMA: Build Any Mac App. Agent skills to research, clone, design, build, optimize, and test native macOS apps with Swift and SwiftUI.项目地址: https://gitcode.com/gh_mirrors/ba/bama
Agent Skills 已经从"新鲜玩法"变成了基础设施。当社区里出现一份 21 个技能、覆盖"调研→克隆→设计→编码→发布"全链路的仓库时,第一反应往往是"这些我自己也能写"——毕竟每个SKILL.md看起来都只是几百行 Markdown。
问题在于,SKILL.md的写作成本极低,而它的维护成本极高。这篇文章不讨论哪种方式更"高级",只做一件事:拿 BAMA 这个真实仓库当样本,把"直接用现成的"和"自己维护一套"两边的账,按时间、风险、决策边界三段摊开,让每一笔支出都能对上具体的文件和行。
先量一下"现成的"这一侧的体积
先把模糊的"一套技能"变成可数的东西。BAMA 当前的规模是:
- 21 个 skill,分布在
start、plan、code、design、ship五个阶段,入口是 skills/start/bama/SKILL.md 这张路由表; skills/目录下 42 个 Markdown 文件,合计 6,341 行,其中 21 个SKILL.md本体占 2,649 行,其余是recipes.md、patterns.md、design-rules.md这类按需加载的参考文件;- 一套可直接构建的 macOS 项目模板,1,384 行 Swift 与 shell,见 skills/start/new-mac-app/template;
- 12 份成品构建计划,2,728 行,见 specs/README.md;
- 全仓库 127 个受版本控制的文件。
这个体量意味着什么?意味着"自建"的起点不是零,而是先要追平 6,000 多行已经被真实项目打磨过的文字。而它的安装成本是一行:
npx skills@latest add daveebbelaar/bama也就是说,获取成本几乎为零,而复制成本是数千行。这个极端的不对称,是后面所有账目的起点。
时间账:安装、改造、跟进更新
安装:四条路线,其中两条会装两遍
README.md 给了四条安装路径:Claude Code 插件市场、Codex 插件、npx skills add、手工git clone后跑 scripts/link-skills.sh。文档里有一句很关键的提醒:
Pick one route per agent. Two routes install every skill twice.
这不是文档写得啰嗦,而是这类"技能包"的固有陷阱——同一份内容可以通过插件系统和符号链接系统同时生效,而多数 agent 运行时不会去重。装两遍的后果不是报错,而是同一个技能被检索到两次,行为变得不可预测。自建一套时你同样要面对这个问题,只不过那时它叫"我本地调试用的软链忘了删"。
手工路线的实现值得看一眼,因为它决定了后面"跟进更新"的成本模型:
for target in ~/.claude/skills ~/.agents/skills; do mkdir -p "$target" for skill in "$repo"/skills/*/*/; do skill=${skill%/} name=${skill:t} ln -sfn "$skill" "$target/$name" done doneln -sfn意味着改仓库即生效:零发版、零版本号、零同步动作。代价是这段脚本用目录 basename 做目标名,skills/design/review和skills/ship/review会映射到同一个~/.claude/skills/review,后者静默覆盖前者。当前 21 个技能名恰好不冲突,但这是一颗随规模增长而必然触发的雷——自建体系最典型的技术债形态:当下正确,扩展即错,且不报错。
改造:每加一个 skill 要改四处
这是自建成本里最容易被低估的一段。AGENTS.md 把"添加一个技能"的流程写死了:
- 新建
skills/<stage>/<name>/SKILL.md和agents/openai.yaml; - 把它加进
.claude-plugin/plugin.json的skills列表、README 的技能表、以及路由表skills/start/bama/SKILL.md; - 跑
claude plugin validate . --strict; - 发布后 bump
version。
也就是说,新增 1 个技能 =至少 4 处结构性改动 + 21 个openai.yaml中的 1 个新文件。把 21 个技能整体改造一遍(换技术栈、换语言、换平台),改动点是 21 × 4 = 84 个同步点。
更麻烦的是,这种重复是设计使然,不是偷懒。README 的技能表按"阶段"分组,路由表按"用户想干什么"分组,两者服务不同的检索路径;agents/openai.yaml里的display_name又服务于 Codex 的 UI,与SKILL.md的name是两套 schema。看这个具体例子:
# skills/start/bama/agents/openai.yaml interface: display_name: "Build Any Mac App" short_description: "Pick the next step for a Mac app" policy: allow_implicit_invocation: false而同一个技能的 SKILL.md 头部用的是另一个字段:
name: bama description: Build any Mac app. Start here to pick the next skill for a native macOS app. disable-model-invocation: true同一个意图("这个技能只许用户手动调用")要在两套 schema 里各写一遍。一旦某个运行时不再识别其中一个字段,退化方式是静默的:从"只能手动调用"变成"模型可以自动调用",不报错、不告警,只是路由技能开始抢占上下文。这类成本不会出现在任何一次"我今天写了多少行"的账上,但它会在半年后以"AI 怎么老是自己乱调技能"的形式出现在工单里。
跟进:被版本号闸门卡住的更新
现成方案最被诟病的是"上游改了我就得跟"。但真实情况比这更微妙。.claude-plugin/plugin.json 的版本号是1.0.0,而 AGENTS.md 里明确写着:
Keep
versioninplugin.jsonat 1.0.0 until the first release. After that, bump it, so installed copies update.
这句话翻译成运维语言是:插件分发路径的更新,完全依赖版本号。上游若改了某个技能的措辞却没 bump 版本,所有通过市场安装的副本都不会更新——用户以为自己用的是新版,实际跑的是旧提示词。这类"看起来更新了"的假更新,比"明显停更"更难排查。
而手工 symlink 路线恰好相反:没有版本概念,也没有回滚概念。仓库 HEAD 是脏的,所有人立刻拿到脏的。两条路线一个太钝、一个太利,中间没有灰度带——这正是自建团队最终都会自己造一套发布流程的原因,而那套流程本身就是新的维护负担。
还有一个现实细节:这个仓库的可见 git 历史只有一条 squash 后的提交(Remove the Glaido recommendation)。对"跟进式维护"来说这不致命,但意味着你无法靠 diff 判断上游改了什么,只能定期重读文档。当改动分散在 42 个 Markdown 里时,"重读一遍"的成本约等于"重新评估一遍"。
运行账:买不掉的那部分
前面都是获取成本和演进成本。真正决定成败的是第三项:每改一次代码要付的运行成本,这一项自建和现成完全一样,谁也省不掉。
BAMA 把它写成了四条硬性完成标准,见 skills/ship/build-and-verify/SKILL.md:
./build.sh # 构建并安装到 ~/Applications tools/snap.sh # 每个页面、每个设置页的明暗两套截图 tools/grid-lint.sh # 间距是否落在 4pt 网格上 swift tools/check-layout.swift /tmp/<App>-shots/*-layout.json注意build.sh里那段看起来多余的检查:
# swift build can report success while an edited file failed to compile. Catch that. stale=$(find "$here/Sources" -name '*.swift' -newer "$bin" | head -1) if [[ -n "$stale" ]]; then echo "build failed: binary is older than $stale"这不是防御性编程的洁癖,而是踩过坑之后的固化:swift build会在某些情况下报成功却产出旧二进制。这种知识,只可能来自真实项目的失败现场。所以这一节的结论是:
- 自建能省掉的是"获取"和"部分演进"成本;
- 自建省不掉"每轮验证"成本,甚至因为缺少成熟检查项而付出更多;
- 现成方案真正卖给你的,不是那 6,000 行文字,而是这 6,000 行里被现实删改过的部分。
同样性质的还有 specs/window-snapper.md 的 Gotchas:AX 与 CG 坐标系在副屏上的翻转差异、AXEnhancedUserInterface让某些应用忽略尺寸修改、默认 6 秒的 AX 消息超时会冻住整个应用、Accessibility 授权绑定代码签名与安装路径导致每次重签都要重新授权。这些条目一条都写不进技术文档的"最佳实践"章节,只能从一个真实上线的菜单栏应用里扒下来。自建一套新体系,第一批 Gotchas 必然是用自己的事故换来的。
风险账:上游停更与上游乱改
停更:这个仓库恰好是不怕停更的那类
评估"上游停更"风险时,要先分清依赖的形态。BAMA 是 MIT 许可、零运行时依赖、零服务端调用、零 SDK 拉取:技能是 Markdown,脚本是 zsh,模板是 Swift Package。上游消失后,已装好的部分继续工作,只是不再有新技能进来。
这跟"依赖某个云端 API 的技能包"是完全不同的风险等级。所以对这类资产,正确的判断不是"会不会停更",而是"停更后我还剩什么"——答案是:剩一份可读、可改、可 diff 的资产。这反而降低了自建的紧迫性。
乱改:真正的上游其实是 Apple
比仓库作者更不稳定的上游,是平台本身。模板的头部就钉死了三个版本:
// swift-tools-version: 6.2 platforms: [.macOS(.v26)], swiftSettings: [.defaultIsolation(MainActor.self)]配套的Info.plist.in里是LSMinimumSystemVersion 26.0。而 skills/start/new-mac-app/SKILL.md 已经预留了降级路径:
On macOS 15, set
.macOS(.v15)inPackage.swiftandLSMinimumSystemVersionto 15 inInfo.plist.in, and skip Liquid Glass APIs.
也就是说,双平台维护从第一天就是既定成本,只是被压成了一句话。等到下一个系统版本发布,这句话要变成三段、五段,而且每一段都要在真实构建里验证一遍。
对比一下两种"上游乱改"的代价:仓库作者改技能措辞,最坏结果是你的 agent 行为漂移;Apple 改平台 API,最坏结果是模板直接构建失败。前者是静默的、需要靠观测发现;后者是响亮的、立刻阻断。从工程角度看,响亮的那一类反而更便宜。
自建侧的风险:静默失败与单点
自建真正的风险不在上游,而在结构:
- 静默失败。上面说的 schema 双写、symlink basename 冲突、版本号不 bump,三者都属于"不报错但行为变了"。
- 单点维护。谁写的技能,谁就是唯一知道"为什么这条规则长这样"的人。BAMA 用 skills/ship/improve-loop/SKILL.md 里的
docs/FIXLOG.md机制对冲这一点——每条修复必须写"症状、原因、修法",最后一行是Rule: <永远要/永远不要做什么>。没有这个机制的自建体系,知识会随人员流动归零。 - 退化无人察觉。这一点可以直接抄现成的答案。skills/code/improve-architecture/SKILL.md 给了可执行的阈值:单个 Swift 文件超 600 行就行动、1000 行必须动,单个类型超 400 行,一个功能改动触及超过 6 个文件,同一区域在修复日志里出现 3 次。自建团队如果连"什么时候该重构技能库"都没有阈值,那它一定会在某次大改后失去可维护性。
决策分界:什么时候买,什么时候自己写
一个能落地的成本公式
把上面的账收拢成一个式子:
总成本 = 获取 + N × 每轮验证 + M × 演进- 获取:现成方案 ≈ 0(一行命令),自建 = 数千行初稿 + 数月打磨。社区里有团队公开过自建四层 Skill 体系的实践,作者的描述是"花了几个月",并给出了 40%–60% 的综合提效——注意这个数字是收益,而几个月的投入是成本,两者要放在同一张表里看。
- 每轮验证:两边相同,且都由项目本身决定,不由技能库决定。
- 演进:现成方案被上游摊薄,但你无法控制节奏;自建则随技能数量线性增长,且没有摊薄效应。
结论很直接:当 N(你实际要跑的迭代轮数)很小,或者 M(平台/上游变动次数)很大时,买;只有当"你的场景在上游根本没有覆盖"时,自建的线性成本才开始划算。
分层:流程层可以买,内容层必须自建
BAMA 的目录结构本身给了一个中间答案:skills/是流程(怎么调研、怎么验证、怎么提交),specs/是内容(具体做一个窗口吸附器还是剪贴板历史)。
这个切分是可以直接借用的。你不必 fork 整个仓库,只需要在自己的项目里放一份 spec——照 specs/README.md 的五段式写(Brief、How it works、Stories、Gotchas、Done when),然后让 agent 拿着现成的流程技能去执行。这样:
- 流程层的维护成本 = 0,上游更新你白拿;
- 内容层的维护成本 = 你自己那一份 spec,几十到几百行;
- Gotchas 的积累落在你自己的 spec 里,不污染上游,也不会被上游覆盖。
"自建"的正确粒度不是"重建一套技能库",而是"只自建那部分别人不可能替你写的知识"。
三档决策表
| 你的处境 | 选择 | 依据 |
|---|---|---|
| 一个人、探索期、目标就是做一个原生 Mac 小工具 | 直接用,别 fork | 获取成本趋零,specs/里 12 份计划已覆盖多数常见形态;fork 的边际收益为负 |
| 2–5 人小团队、场景与上游高度重合,但需要统一风格与验收口径 | 用现成的 + 只写自己的CLAUDE.md/ spec | 模板里 CLAUDE.md 已经把设计规则、构建命令、快照要求固化,覆盖面远超口头约定 |
| 垂直领域(内部框架、特定合规、非 macOS 平台)、上游完全无覆盖 | 自建,但先只建"内容层 + 少量护栏" | 先写 3–5 个真正会重复用的技能,跑满一个里程碑再决定要不要扩到 21 个 |
| 团队 > 5 人、需要审计与灰度 | fork 成内部仓库,把上游当依赖定期 rebase | 必须同时补三样东西:版本号 bump 规则、技能冲突检测、FIXLOG式的知识沉淀 |
最后一条要展开一句:fork 不等于复制粘贴。一旦你 fork,你就继承了 84 个同步点的维护义务,同时也继承了上游所有静默失败模式。如果团队规模撑不起这套义务,fork 出来的东西会在三个月内变成一份没人敢动的祖传配置——那才是自建最贵的结局。
所以回到标题:自建一套 vs 直接用现成的,真正的分界不在"我能不能写出来",而在"我能不能负担它每周要我付的那笔账"。写出来是一次性的,维护是永久的。
【免费下载链接】bamaBAMA: Build Any Mac App. Agent skills to research, clone, design, build, optimize, and test native macOS apps with Swift and SwiftUI.项目地址: https://gitcode.com/gh_mirrors/ba/bama
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考