我决定做一个面向程序员的开发机体检 skill,代号 devbox-doctor。它只读扫描你的 Mac,用 Spotlight 使用记录和包管理器数据做证据,让模型判定三类问题——僵尸应用、堆叠冲突、卸载残留——再给出同生态位的现代替代建议,最后产出一份三灯分级、可以一键处置、也可以截图分享的体检报告。这篇文章是它的完整设计文档:需求怎么验证、数据层和判断层怎么划、替代建议怎么做才不翻车、仓库长什么样。
上一篇 我拆了一个敢删我文件的存储清理 skill,得到一套设计方法论。方法论不落地就是收藏夹,这篇是它的第一次完整应用——而且需求不是想出来的,是从我自己的机器上长出来的。
需求是从我的磁盘里挖出来的
起因还是那次磁盘清理。扫完 296 GB 已用空间,除了预期内的缓存,报告里躺着几个让我坐直了的条目:
- Docker Desktop 和 OrbStack 并存,一个占着 4.4 GB 虚拟磁盘,一个占着 12 GB 数据——两个都是容器运行时,我日常只用其中一个,另一个纯粹是「换工具的时候忘了送走前任」;
- Trae 编辑器的残留:应用本体早就卸了,
Application Support里躺着 2.5 GB 数据,~/.marscode还有 1.2 GB——卸载动作只完成了一半; - pyenv 里囤着 3.6 GB 的 Python,而我今年的新项目已经全面转向 uv;
- 还有一排「装过、试过、再没打开过」的工具:几个 AI 编辑器、几个终端、若干效率小工具。
把这几条放在一起看,一个模式浮出来了:**程序员的电脑是工具的墓地。**我们是全世界装软件最勤、卸软件最懒的人群。每一次技术选型、每一波工具热点、每一篇「XX 神器安利」都会在机器上留一具尸体:旧的运行时、被替代的 CLI、试用完忘掉的 App、迁移到一半的前任工具。单看每一次安装都有理由,日积月累就是几十 GB 的磁盘、一排开机自启动、和一个越来越说不清「我机器上到底装了什么」的你。
而且这个问题有个微妙的性质:**它不是靠「清理」能解决的。**清缓存是体力活,判断「这个工具还该不该活在我机器上」是脑力活——它需要知道这个工具是什么、生态位上有没有更好的、我到底还在不在用它。这正是上一篇里那个判断的完美测试场:确定性工作交代码,判断交模型。
过三关:这个需求配得上一个 Skill
按方法论,先过三关。
**高频吗?**装软件是程序员的日常动作,而审视存量几乎从不发生——不是不想,是成本太高:逐个 App 回忆「我上次打开它是什么时候」不现实。高频的债务积累 + 从不执行的盘点 = 典型的该被 skill 接管的流程。
**有非模型不可的判断吗?**这是 devbox-doctor 比存储清理更依赖模型的地方。「OrbStack 和 Docker Desktop 是同生态位竞品」「fnm 是 nvm 的现代替代」「这个叫某某清理大师的东西是伪装成工具的广告位」——每一条都是世界知识,写不成规则表。尤其是用户要的那个增量:不只告诉我什么该删,还告诉我同类里现在什么更好——替代建议本质上是一次小型技术选型咨询,这是纯粹的模型生态位。
**有明确交付物吗?**一份体检报告:三灯分级的处置清单 + 第四区的升级建议 + 一张可分享的统计卡。可验收(报告里每一项都能核实)、可迭代(判断错了改参考文档)。
三关全过。开工。
数据层设计:你的 Mac 一直在记账
体检的第一原则和上一篇一致:**扫描全程只读。**但这次的数据面比磁盘大小丰富得多——判断「还在不在用」需要使用证据,而 macOS 恰好一直在记账,只是没人去看:
scan.py 只读采集六类事实
├── 应用清单 /Applications + mdls 逐个读元数据
│ (kMDItemLastUsedDate 最后使用时间 /
│ kMDItemUseCount 使用次数 / 大小 / 版本)
├── 包管理器 brew list、brew leaves(孤儿依赖)、
│ brew autoremove --dry-run、npm -g、pipx list
├── 版本管理器 pyenv versions、nvm ls、rustup toolchain list——
│ 同类工具装了几套、各囤了多少版本
├── 自启动 LaunchAgents / LaunchDaemons / 登录项——
│ 谁在开机时偷偷复活
├── 残留匹配 Application Support / Containers 里的目录
│ 与在装应用清单求差集 → 孤儿数据
└── 运行证据 当前进程表——哪些「没在用」的东西其实常驻后台
mdls 是整个数据层的惊喜。我在自己机器上验证:Discord.app 返回「最后使用 7 月 15 日,累计 2 次」——系统级的使用记录,只读可得,零依赖。
但验证也立刻暴露了一个坑:Xcode.app 和 Pages.app 返回 null,而我明明常用 Xcode。原因是部分应用不经由启动服务打开(命令行拉起、后台常驻),Spotlight 就不记账。这个坑直接变成一条设计规则写进 SKILL.md:**null 不等于没用过。僵尸判定必须多源证据交叉——使用记录、数据目录的修改时间、进程表、终端 history 里的调用痕迹,至少两源同向才能给出「疑似僵尸」,单源永远不定罪。**证据不足时宁可归入「待观察」,也不进删除清单。
这条规则的本质,是把上一篇「模型只提议」的原则又往前推了一步:模型的提议本身也要有证据链,不能让它凭「这个 App 名字听起来过时了」来判死刑。
判断层设计:三灯之外,加一个「升级区」
扫描 JSON 交给模型后,判断分四步走:
**第一步,逐个识别生态位。**每个 App、每个 CLI 是什么、属于哪一类(编辑器/终端/容器运行时/版本管理器/笔记/效率工具)。这一步纯吃世界知识。
**第二步,三类顽疾判定。**僵尸应用(多源证据交叉,见上);堆叠冲突(同生态位 ≥2 个且都有近期使用痕迹的,列出来让用户二选一;只有一个在用的,另一个转僵尸处理);卸载残留(残留匹配差集直接给出,这是最接近确定性的一类)。
**第三步,低价值与流氓判定。**捆绑安装的全家桶、伪装成系统工具的广告容器、明明有开源平替却常驻收费弹窗的——这类判定敏感度最高,所以证据要求也最高:必须同时命中「行为证据」(自启动、常驻进程、弹窗记录)和「生态位证据」(存在公认的干净替代)才上灯,并且在报告里写明判定依据。
**第四步,也是这个 skill 的差异化增量——升级建议。**对每一个「在用且健康」的工具,模型回答一个问题:**同生态位里,现在有没有更流行、更快、性价比更高的选择?**比如:还在 nvm?fnm 和 mise 启动快一个量级。还在 Postman?Bruno 离线优先且配置进 git。三套 Python 工具链?uv 一个顶五个。
报告形态相应地从三灯变成「三灯 + 一区」:
| 区域 | 语义 | 动作 |
|---|---|---|
| 🟢 可直接清 | 卸载残留、孤儿依赖、缓存 | 一键移废纸篓 / 展示 brew 命令 |
| 🟡 需要决断 | 堆叠冲突二选一、疑似僵尸 | 展示证据,在访达打开,用户定夺 |
| 🔴 只引导 | 该走正规卸载的大件、带自启动的顽固分子 | 定位到 App,给分步卸载指引 |
| ⬆️ 升级建议 | 在用工具的同生态位更优解 | 纯情报卡片,不带任何删除按钮 |
升级区和三灯有一条铁的边界:**它不是删除决策,永远不出现在处置清单里。**它是你顺手做的一次技术选型雷达扫描,看完可以完全无视。
替代建议是最容易翻车的一区,所以规则最严
设计到这里,我停下来想了很久的不是技术,是一个产品伦理问题:AI 张口推荐软件,和营销号带节奏之间,隔着什么?
隔着产生条件的约束。我给升级区定了四条规则,直接写进 SKILL.md 的铁律区:
- **只推荐同生态位、活跃维护的工具。**不跨类安利(「你该试试 Rust」不是升级建议),不推荐停更项目;
- 必须写迁移成本。「fnm 兼容 .nvmrc,迁移十分钟」和「换构建系统,迁移一周起」是完全不同的建议,不写成本的推荐是耍流氓;
- **必须给出不换的理由。**每张卡片都有「什么情况下别换」一栏——推荐的职责是呈现权衡,不是替用户下结论;
- **绝不建议换掉正在重度使用的付费工具。**使用证据优先于流行度:你天天用的 JetBrains 全家桶,哪怕全网都在吹别的,它也不该出现在升级区。花了钱且在用,就是最好的选型证明。
一句话收束:**推荐是情报,不是钦定。**模型负责把「同类工具的现状」摆上桌,拍板永远是人的事——这其实还是那个三权分立,只是从「删不删」延伸到了「换不换」。
知识会过期,所以参考文档带日期戳
升级建议还有一个三灯没有的难题:**工具生态知识是易腐品。**今天的「现代替代」两年后就是新的僵尸。如果把「fnm 优于 nvm」写死在 prompt 里,这个 skill 会以肉眼可见的速度烂掉。
解法是把易腐知识从 SKILL.md 里剥出去,放进独立的参考文档,并且每一条都带判断日期:
devbox-doctor/
├── SKILL.md # 流程与铁律(稳定,很少改)
├── references/
│ ├── macos.md # 数据怎么读:mdls 的坑、残留匹配规则(稳定)
│ ├── stacks.md # 生态位地图:每类工具的主流选择与堆叠模式
│ │ #(易腐!每条带日期戳,定期重审)
│ └── judging.md # 判定标准:僵尸证据链、流氓判定条件、
│ # 升级区四条铁律(稳定)
├── scripts/
│ ├── scan.py # 只读扫描六类事实 → JSON
│ ├── build_report.py # 分析 JSON → 静态报告
│ └── server.py # 本地服务:白名单一键处置(沿用七道锁)
└── assets/
└── report_template.html
SKILL.md 里的指令变成:「判断堆叠与替代时,读 stacks.md,注意每条的日期戳,超过一年的条目要在报告里标注可能过时」。这个设计顺手回答了一个更大的问题——**skill 和博客文章的根本区别是什么?**文章发布即冻结,而 skill 的知识层是活的:发现 stacks.md 里某条过时了,改一行,所有后续体检立即受益。上一篇说「Skill 是流程的固化」,这篇要补一句:固化的是流程,活着的是知识。
传播设计是一等公民,不是发布后的运营
上一篇分析过 storage-analyzer 的传播齿轮:结果可截图、人人相关、有惊讶感、零依赖。devbox-doctor 把这四个齿轮直接做进产品:
可截图:报告顶部是一张「体检卡」——堆叠冲突 N 组、僵尸应用 N 个、卸载残留 N GB、升级建议 N 条,四个数字加一个「机器健康分」。卡片上只有统计数字,没有任何路径和应用名——可以放心发到任何群里,隐私焦虑为零。这行设计约束和「三个统计值要以可解析数字开头」一样,写进 SKILL.md 的输出规范。
有惊讶感:程序员看到「你的 Mac 一直在记录每个 App 的使用次数」的第一反应就是掏出终端跑一遍 mdls——这个知识点本身就是传播素材。「三套 Python 版本管理器在打架」「卸了半年的编辑器还躺着 3.7 GB」都是天然的谈资标题。
人人相关:圈层比全民清理窄,但密度高——每个程序员都有工具墓地,且程序员是最愿意晒终端截图的人群。
零依赖:继承 storage-analyzer 的标准库原则,mdls、brew、osascript 全是系统自带或早已在装。
泼冷水:图纸不是产品
照例给自己泼两盆。
第一盆:**这篇是设计文档,不是发布公告。**上一篇方法论里最后那条军规——「没手动跑通过的流程不配被固化」——对我自己同样生效。数据层的每个采集点我验证过了(mdls 的 null 坑就是验证的战利品),但判断层的准确率、升级建议的翻车率、报告的实际观感,都要等真机跑过足够多轮才算数。做完我会回来写实测篇,到时候这篇里被现实打脸的设计,会原样展示。
第二盆:**升级区是整个设计里我最没把握的部分。**四条铁律防得住恶意,防不住平庸——模型完全可能给出一堆「正确但无用」的建议(是的我知道 uv 很好,我半年前就换了)。解法可能是让它先读机器上的实际版本和配置再开口,但这会显著加大扫描面。这个权衡我还没想清楚,先诚实记在这里。
**工具的墓地人人都有,但守墓人这个职位,今天终于可以外包给 AI 了。**仓库开工后见。



读者回响