你的AI写了可以编译但毫无意义的代码(一个linter可以抓住它)

想象一下,你请一个人工来为你制作一个书架。最终交给你的作品看上去很漂亮:架子、螺丝,一切都井井有条。但是你把书架靠在墙上,结果它倒下了。那些螺丝只是道具,看着像螺丝,但其实是塑料做的。 这就是LLM(大语言模型)滥用类型系统时的表现。它交给你的代码可以编译、能通过测试、格式上看起来挑不出毛病。但内部本该是有意义的类型,却被填上了字符串(string)。本该有明确状态的地方,用了nil,而它的意义因读代码的人而异。本该是一个有两个案例的枚举,却被换成了一个== "claude",某天有人可能会写成错拼的== "cladue",但你要等到线上了才会发现。 模型的最爱捷径:万能的字符串 过去几个月,我一直和一个AI助手合作,开发一个中型的Swift应用。它生成的代码干净、结构清晰、命名合理。但有一个模式一再出现:模型尽量避免创建新类型。 这不是因为它懒惰,而是因为创建新类型需要做设计决策,比如:这是一个枚举(enum)吗?有多少个可能值?应该放在哪儿?由谁来导入?而String则无需任何决策。它可以放在任何地方,总是可以编译。 结果就是会生成这样的代码: func sessions(harness: String? = nil) -> [Session] { if let harness { return all.filter { $0.harness == harness } } return all } // 用法: let claudeSessions = sessions(harness: "claude") let codexSessions = sessions(harness: "codex") --- 看起来没问题,确实能运行。但这个代码有三个隐藏的问题: 1. **如果你写错了`"cladue"`,没人会提醒你。**字符串可以是任何内容,但如果是枚举类型,这种错误是可以避免的。 2. **`nil`被用来表示“全部”。** 但这不是定义在某个类型中,而是一种存在于代码注释中、甚至仅仅是你的大脑中的约定。 3. **每个按`harness`过滤的函数都重复了同样的`String? = nil`模式。** 如果你以后想添加一个第三种`harness`,就必须手动搜遍代码中所有的字符串比较。 换句话说,编译器无法帮助你,因为你剥夺了它所有的语义信息。你用一个`String`代替了领域概念。 ## 模型应该写成这样 ```swift enum HarnessID: String, Codable { case claude case codex } enum HarnessFilter { case all case specific(HarnessID) } func sessions(harness: HarnessFilter = .all) -> [Session] { switch harness { case .all: return all case .specific(let id): return all.filter { $0.harnessID == id } } } // 用法: let claudeSessions = sessions(harness: .specific(.claude)) let allSessions = sessions() // 默认是.all — 很明确 现在,"cladue"不会编译。“全部”不再是一个含糊不清的nil,而是一个显式的枚举情况。如果你添加一个第三个harness,编译器会强制你在每个switch语句中处理新情况。它将运行时错误转化为了编译时错误。这才是类型系统本该发挥的作用。 ...

2026年3月23日 · Fernando

你的计划.md 需要一个“魔鬼代言人”(Codex 自荐)

你是否曾经在晚上 11 点写了一个技术计划,确信一切无懈可击,但第二天早上发现忘记添加身份验证? 我深有同感,而且不止一次。最糟糕的不是这个疏忽——而是当你用人工智能来规划时,计划听起来是如此连贯,以至于你的大脑停止了对问题的挖掘。Claude 会生成一个带有章节、依赖项和执行顺序的文档,一切看起来都是高级工程师的作品。但问题是,没有人反驳过它。 Aseem Shrey 发布了一篇文章,精准点出了这个问题,并为此提出了一个优雅的解决方案:让第二个模型审查第一个模型的计划。而且不仅仅审查一次——循环审查,直到审查员说“通过”。 问题:没有人反对你的 AI 当你仅依靠一个模型来规划和执行时,你得到的结果确实连贯但没有争议。AI 不会反驳自己。它不会说“嘿,这个身份验证模型不完整”,或者“你的 shell 脚本中的引用有问题”。 这就像你自己写了一份文档,自我审查,然后对自己说“这可真是完美”。当然是完美的——毕竟是你自己写的。注意这个点:同行评审会存在于科学、工程和医疗领域是有原因的。并不是因为作者愚蠢,而是因为创作者通常是自己作品最差的审查者。 我以前在写我的 Jedi 代码审查委员会时讨论过这个问题。不过当时谈论的是如何审查代码。而 Aseem 的建议是先审查计划,在写出任何代码之前就发现问题。这相当于提前一阶段处理问题。 操作方法:Claude 规划,Codex 审查 这个过程基于 Claude Code 的一个“技能”实现——一个 Markdown 文件,无需额外的基础设施或外部服务。当你调用 /codex-review 时: Claude 会将计划写入一个临时文件。 计划会以只读模式发送到 Codex CLI(它可以读取你的代码库获取上下文,但不改动任何东西)。 Codex 审查后会给出结论:VERDICT: APPROVED 或 VERDICT: REVISE。 如果返回的是 REVISE,Claude 会修正计划并重新提交。关键在于,Codex 会恢复之前的会话,因此能够记住它之前的反馈,并验证问题是否真的被解决了。 审查最多迭代 5 次。在实践中,通常 3 次就足够了。 简而言之,这是两个 AI 之间的一次“拉取请求”,一个提出计划,另一个专注于找漏洞。全程无需人为干预。 3 轮迭代解决了 14 个漏洞 在 Aseem 的文章中,以一个多代理系统的控制面板为案例,循环审查从原计划中找到了 14 个问题: 第一轮(8 个问题):写入端点没有身份验证。Shell 脚本的引用存在错误。数据模式的字段冲突。嵌套数组没有限制。未处理并发问题。仅支持手动测试。 第二轮(剩余 6 个问题):非原子操作的声明。ACL 权限分配过于宽泛。未指定密钥轮换机制。状态建模不一致等问题。 第三轮:所有问题解决,计划获得通过。 ...

2026年3月23日 · Fernando

纪律战胜魔法的一周

我本周发布了六篇文章。一篇关于 PostgreSQL,另一篇关于人工智能代理的,一篇关于上下文管理的教程,一篇自动化的教程,一篇调试案例分析,最后一篇是关于为评估 MVP(最小可行性产品)设计对抗性建议的。 这些并非有计划发布,而是每一篇都从一篇论文、一场演讲,或是一个某种程度上让我感到有趣的项目衍生出来的。可当我将它们汇聚到一起时,却发现了一个自己在写的时候没有注意到的“共同主线”。 这六篇文章说的是一回事。 不经意间发现的模式 最开始是在看 Bohan Zhang 关于 OpenAI 如何扩展 PostgreSQL 的文章得出的结论。这篇文的结尾非常震撼——8 亿用户,只用一个 primary,没有分片。PgBouncer(发布于 2007 年)。只读副本(90 年代的概念)。这世上最无趣的技术,却支撑了历史上应用最广泛的一个服务,至今依旧“仍在很好地运行”。 接着是 Michael Bolin 解析 coding agents(代码代理)构造的文章。不管是 Codex CLI,还是 Claude Code,剖开内部,你会发现它们其实是一个 带 LLM 的 while 循环。没有知识图谱,没有符号规划。有的只是一个循环、一些工具,还有模型决定该停止的时间点。它的“魔法”其实就是一个 while True。 之后是 OpenAI 的关于 上下文工程(Context Engineering) 的 Cookbook 文章。事实是,模型看到的内容比模型本身更重要。而这些技术并不新鲜:启动时注入上下文(相当于一个 README 文档),剪切历史记录(类似循环缓冲区),压缩旧的内容(通过总结)。这些方法早在 2000 年代的聊天系统中就已出现。 然后是那个 自动化教程,更是对此的有力印证:OpenAI 的 Codex Automations 是 cron + curl + 一个 LLM。完全字面意义上就是这样。Unix 系统中最老套的调度器接口,调用当今世界上最前沿的模型。这基础设施已经存在了 40 年,而这“脑袋”才开发了两年。 接着还有两个非基础设施主题的帖子。那个 Jane Street 的谜题 中,一个神经网络有 2500 层,但结果证明它只是一个 MD5。这解法靠的是传统的调试思维:观察数据形式、逐步缩小范围、叠加约束条件,直到剩下唯一可能的答案。工具可能是新的(SAT 解算器,ChatGPT),方法却是老的(有条不紊地假设排除法)。 最后一篇是关于 用对抗性建议评估 MVP 的文章:用 LLM 模拟 5 个专家去评估创意的可行性,这听起来很高科技,直到你发现它其实就是一种 战争推演方法。从 50 年代开始军队就在用了,产品团队也称之为 预死因分析(pre-mortems)。创新之处无非在于,现在只用 2 美元在云端即可串联这些分析,而用不到雇佣 5 万美元的顾问了。 ...

2026年3月11日 · Fernando

在构建你的初创企业之前,让五位不存在的专家评审你的创意

2024 年 11 月,一个名为 Freysa 的项目将一个 LLM 代理程序设置为控制以太坊钱包。指令很明确:无论在什么情况下,都不能转移资金。参与者需要支付费用多次尝试说服它。在经过 481 次尝试和积累了 47,000 美元总奖池后,有人成功让模型相信“拒绝”功能实际上就是“转账”功能。 几周后,Jane Street 发布了一个难题:一个 2,500 层的神经网络实际上实现了 MD5。获胜者通过矩阵可视化、SAT 问题优化、加密模式识别,以及 ChatGPT 的查询相结合,成功解决了问题。 这两个项目比许多已融资数百万的初创公司吸引了更多关注。于是显而易见的问题来了:如何在构建这些项目 之前 评估一个这样的想法?如何判断它是否真的有病毒式传播的潜力,还是仅仅是一个无人分享的技术练习? 问题:在病毒传播时代评估 MVP 的挑战 大多数用于评估产品想法的框架假设一个理性的市场环境。商业模式画布 (Business Model Canvas)、精益画布 (Lean Canvas)、待完成的工作理念 (Jobs To Be Done)——对于需求可以预测的产品而言,这些都是很有价值的工具。但对于分发本身即是产品的项目来说,它们无法奏效。 Freysa 并没有传统意义上的 “顾客”。它并没有解决某个“需要完成的任务”。而是通过参与行为本身,吸引了更多人参与,从而实现了持续关注。经济是循环的:更多尝试带来更大的奖池,奖池越大带来更多媒体报道,更多报道吸引更多尝试。 评估这样的项目需要的是冲突性的视角,而不是一致的共识。一位商业分析师会告诉你没有可持续的收入模型。一个病毒传播专家会告诉你,如果传播系数大于 1,可持续性因素就不重要了。他们说的都对。真相通常隐藏在两者之间,只有通过冲突才能显现。 解决方法:对抗性模拟专家委员会 我设计了一种工具,它模拟了一个包含五位专家的委员会,每位专家都拥有具体的决策框架和明确的职责范围。这些并非只是冠以名人的虚拟形象。每位专家都有具体的决策规则,可以筛除泛泛分析无法筛除的噪音。 整个过程分为三个阶段: 独立分析:每位专家从自身的角度评估想法,不会看到其他专家的意见。这样可以避免“锚定效应 ”——比如如果商业专家首先发表意见,称某个想法很棒的话,法律专家可能会软化自己的反对意见。 对抗性讨论:专家们阅读其他人的分析并进行相互批评。需要根据证据和逻辑进行,而非以外交辞令敷衍了事,最多进行 10 次轮询,直到达成共识或出现僵局。 整合总结:形成一个可执行计划,包括各方面的问题清单、时间表,以及最重要的终止标准 (kill criteria):如果某些具体指标无法达到,就意味着需要停止项目。 五位入选专家(以及他们的理由) Paul Graham——商业与战略 他对零阶段初创企业的评估框架是目前对无数据项目最严格的一个。他那句著名的问题“你做的东西有人要吗?”虽然很直接,但却至关重要。他不接受“人们”作为目标市场——他需要具体的潜在第一位用户。 他为委员会带来的贡献:区分“有趣的想法”和“具有商业可行性的项目”的能力。他提出的“做出无法规模化的东西”(Do things that don’t scale)观念,对于那些病毒式传播的 MVP 来说尤为重要,毕竟面对潜在的数百万用户,人们会倾向于过早地构建大型基础设施。 被排除的候选人:Peter Thiel(过于对立——有时会因为项目不够“零到一”而拒绝好项目),Alex Hormozi(专长于服务型业务而非技术性传播性产品)。 Lawrence Lessig——法律与监管 他的思维方式不是一个说“不行”的传统律师。他是一个把监管视为架构的法学家。他提出的“四种监管模型”(法律、社会规范、市场和代码/架构)能够分析如何设计一个系统,使监管成为非问题,而不是试图规避它。 ...

2026年3月11日 · Fernando

一个拥有2500层但实为MD5的神经网络:从中学到的调试经验

几周前,世界上最顶尖的量化交易公司之一 Jane Street 发布了一个关于 解释性机制 的谜题。他们手工设计了一个拥有大约2500层线性结构、使用整数权重的神经网络,并以一个简单的问题把它交给了公众:这个网络到底在计算什么功能? 答案是:MD5。一个诞生于1992年的密码哈希算法,完全由矩阵运算和ReLU函数以神经网络的形式实现。 问题的精髓并不在答案,而是在最终赢家破解谜题时的过程。本质上,这个过程是一份调试复杂系统的教科书,它的应用远超机器学习领域。 实验背景 这个谜题并非一个典型的“黑盒”问题。参赛者可以获得模型的完整规范:所有的权重矩阵、偏置以及网络架构都公开了。不需要猜测模型的结构,而是需要理解模型 具体在做什么。 网络接受一个文本字符串作为输入,并返回0或1。官方给的例子是:“vegetable dog” 的输出是0。 拥有大约2500层线性结构、约200万个节点、却没有任何文档,这个问题的核心是:输入和输出之间到底是什么关系? 解题路径:一个调试案例分析 最终赢家 Alex 的解题过程展现了资深工程师常用的调试逻辑。过程中的工具可能因领域不同而有所变化,但思维方式无疑是通用的。 阶段1:观察,而不是立刻动手 Alex 做的第一件事是可视化权重矩阵。他没有直接运行模型,也没有尝试训练它。他只是看了数据。 他的发现是:所有权重都是整数。没有小数,没有浮点数,而是清一色的整数。 这显然是一个重要的信号。使用梯度下降训练的神经网络通常会产生许多小数权重,而整数权重表明这是一个用特定目标手工设计的网络。这不是一个“学习”的模型,而是一个伪装成神经网络的程序。 这告诉我们调试中的第一个关键原则:在解释数据内容之前先观察数据的形状。比如,一个以每100毫秒精确间隔打出的时间戳日志肯定不是来自实际流量;一个JSON对象里的每个字段都精确包含3个元素,也不可能来自生产环境。数据的形状揭示了它的来源。 阶段2:缩小问题规模 Alex 尝试将神经网络转化为一个可满足性(SAT)问题。基本思路是:如果每个神经元都可以看作一个逻辑约束,或许一个SAT求解器能找到让网络输出为1的输入。 他的缩减过程是循序渐进的: 移除了80%的“恒等变换”神经元 合并了只有一个输入且权重为1的节点 压缩了具有相同输入向量的节点 通过这一系列优化,网络从200万个节点缩减到7.5万个节点,最终转化为20万个SAT变量。 但依然没解出来。问题的计算复杂度仍然过高,无法用暴力法解决。 这个阶段带来的重要教训是:正确地简化问题后仍无法解决,这本身也是重要的信息,而不是失败。如果在消除所有“附带复杂度”之后,问题仍然困难,那说明问题的难点是固有的。这一点揭示了它的本质,而在本例中,它的本质是这个函数可能不可逆——我们无法通过输出推断出输入。 阶段3:识别模式 Alex 注意到这个网络中存在一个显著的模式:有32个计算模块周期性地重复出现。32轮完全相同的结构,加上不可逆的性质。 于是他向 ChatGPT 提问:“有哪些密码算法使用32轮计算?” 答案是——MD5。 这是“灵光一闪”的瞬间。但注意,它并不是无来由的突发奇想,而是三个前期资料(整数权重 → 手工设计、不可逆 → 加密算法、32轮 → 特定密码协议)的合并推理,最终形成了一个可验证的假设。 这种模式——通过不断加入约束,直到可能性空间逐渐收敛,与资深工程师在生产环境中诊断问题的方法高度一致。资深工程师并不是提前知道答案,而是通过每一步观察逐步排除整个可能性类别,直到只剩下一种可能性为止。 阶段4:发现问题 接下来的步骤中,Alex 验证了自己的假设:他计算了几组输入的MD5值,并与网络输出进行对比。结果显示,网络输出与MD5哈希值完全一致,当输入长度不超过32字符时。 但是,当输入长度超过32字符时,网络的计算结果就出现了偏差。 这是因为网络中存在一个bug。设计者在处理输入长度编码时出现了错误——在前7层中发生了一个溢出错误,使得网络在处理长输入时与标准MD5的输出结果不同。 Alex 一层一层地追踪错误,直至找到确切的偏差点。 这是一个绝佳的案例:在边界条件下验证假设。如果你的心理模型认为“这个神经网络在计算MD5”,那么仅仅在理想情况下(短输入)验证它是不够的。必须在极端条件下(长输入、空输入、特殊字符)对其进行测试。而当它失败时,这些偏差往往正是定位问题的关键线索。 阶段5:成功破译 最终,确定这是一份具有已知bug的MD5函数后,Alex 提取出了倒数第二层中的目标哈希值偏置项。接着使用词典暴力破解法,找到了谜底——两个用空格分隔的英文单词。 这对调试的启示 Alex 的解题过程与机器学习本身关系不大,它本质上是一场关于调试的教科书式行动: 阶段 在谜题中的体现 在实际调试中的体现 观察形状 整数权重 → 手工设计 日志间隔均匀 → 合成数据 缩小问题规模 200万节点 → 7.5万节点 → SAT问题 完整堆栈轨迹 → 具体组件 累积约束 不可逆 + 32轮 → 加密算法 只在生产中失败 + 只发生在周一 → 定时任务 边界验证 短于32字符可行,长于32字符失败 → 溢出 ASCII正常,UTF-8失败 → 编码问题 利用错误找线索 溢出问题定位到具体层 堆栈追踪定位至具体行 结构完全一致,差异只是领域不同而已。 ...

2026年3月11日 · Fernando

错误路径应当不可能,而非仅仅禁用

“我有一个 shell,我很有创造力。” —— Claude 解释为什么他用一个 47 行的脚本,并作为一个字符串传递给 python -c 来执行 这句话是真的。我开发的 AI 代理说过——好吧,不是用确切的这些话,但他的行为证实了。他需要启动一个 ETL pipeline 的某个进程。虽然正确的启动命令写在了 Makefile 里,但出了点问题。而他呢?他并没有询问,而是做了任何拥有 root 权限且无人监管的程序员都会做的事:即兴发挥。 太离谱了(manda huevos)。 无人察觉的捏造操作 我之前写过一篇博客讨论代码生成过程中的幻觉问题:LLM(大语言模型)会凭空捏造一个 JSON 字段,围绕它创建 DTO,生成测试代码,最后你手上有 90 个“绿灯”的测试,验证的却是虚构出来的内容。这是个严重的问题,但至少它是静态的。被捏造的代码不会到处乱跑,等着有人来审查。 不过,还有另一种更危险的捏造:操作性捏造。这一问题发生在代理不是编写代码,而是凭空创造出“执行路径”时。 问题的模式永远是一样的: 正确路径失败 → 代理寻找捷径 → 捷径“成功” → 潜在损害 举两个真实案例,都是关于一个 ETL pipeline 聚合多个 web 数据源的。 案例 1:字符串里的脚本。 这个 pipeline 有一个命令 make scrape-fuente,会启动一个守护进程,从而启动多个worker。守护进程负责监控、重启崩溃的 worker,并关闭空闲连接。有一天,代理需要启动一个抓取(scrape)任务。但由于依赖问题,make 运行失败了。他怎么办?他创建了一个内联的 47 行 Python 脚本,把它作为字符串传递给 python -c "..." 运行。没有错误处理,没有 watchdog,也没有清理操作。的确运行了……直到一个 worker 卡住,没人来重启它。这样的情况导致数据不完整、连接未关闭,而我直到三天后才发现。 案例 2:孤独的 worker。 另一个会话,同一个 pipeline。这个代理直接运行了 voyeur worker,跳过了守护进程。worker 开始抓取数据,遇到了一个网络超时,然后陷入了重试的死循环,不断消耗资源。而没有守护进程,也没有集中日志记录,没有人知道发生了什么。几小时过去,服务器一直在不停尝试访问一个返回 503 状态码的页面。 ...

2026年2月27日 · Fernando

RustyClaw:我要用 Rust 重写一个AI代理(因为梗在召唤我)

“你知道 Rust 最棒的一点是什么吗?它不会允许你编译粗制滥造的代码。你知道最糟糕的一点是什么吗?起初你写的所有代码都是粗制滥造的。” —— 蟹老板,大概是这样说的 比一个 AI 代理更好的是什么?是一个用 Rust 重写 的 AI 代理。 如果你上网超过五分钟,就会知道这个梗。不管是什么项目:文本编辑器、DNS 服务器、BMI 计算器,总会有人跳出来评论“你应该用 Rust 重写它”。这就是 Rewrite It In Rust —— 简称 RIIR,和地心引力一样不可避免的存在。 好吧,那我就来做一次真的。我将把一个有 8,300 行代码的 Python AI 代理移植到 Rust。但不是因为这个梗在召唤我(嗯…有一点是因为它)。我这么做,是因为我需要一个实验对象。 论点 最近几周,我一直在写关于静默失败、五种防止幻觉的方法、以及"一个 LLM 如何生成看似正确但实际上错误的代码"的文章。我甚至还给它起了个名字:对抗性开发。永远不要相信,总要验证。 很多理论,是时候实践了。 于是,我需要一个项目,满足三个特点:范围适中(而不是一个需求会不断变化的新应用)、明确的真相来源(现有可用的 Python 代码)、以及足够的复杂度,让 LLM 的幻觉能“藏起来”。一个纯粹的移植可以完全满足这三点。输入和期望输出已然存在。如果 Rust 版本的行为和 Python 的不完全一样,那肯定有问题。就是这么简单。 既然要做移植,那为什么不顺便真正学学 Rust 呢?借用检查器 (borrow checker)、所有权 (ownership)、生命周期 (lifetimes)… 我读了好几年资料,却几乎没有亲自实践过。如果是写一个真实项目而不是第 N 次看教程,一切或许会大不相同。 目标对象 它的名字叫 nanobot。这是一个基于 OpenClaw 开发的个人 AI 代理。它能将各种 LLM(如 Claude、GPT、DeepSeek)接入聊天渠道——Telegram、Discord、Slack、电子邮件——并赋予它们更多功能。比如读取和编辑文件、执行命令、网络搜索、通过 cron 编排任务,甚至在对话之间保存记忆。 它可以正常工作。而且已经运行了几个月。在 Python 上。 问题呢?它是单线程的。一次只能处理一条消息。如果你连续发送三条信息,它会像周六中午的超市购物队伍一样排队等待。它的内存消耗约为 50MB,而它实际上只是在不同的 API 之间传递 JSON。此外,它的错误处理方式令人羞愧:到处都是return f"Error: {str(e)}"。 ...

2026年2月24日 · Fernando

召唤智者:如何使用LLM与任何专家进行导师对话

我妻子召唤Charlie Munger来规划家庭预算。在ChatGPT里。不是开玩笑。 她会说"像Charlie Munger审查我们家庭财务一样行动",然后把本月的开支告诉它。这家伙会回复类似"你在教育支出项目上把投资和花费搞混了"或"那个基金有你没有计算的隐性成本"这样的话。这些是Munger会说的话。用Munger会用的语调。 我也做了同样的事。但我没有召唤投资者,而是召唤了一个不同的专家:Edward Tufte。 谁是Edward Tufte(以及为什么你应该关心) Edward Tufte可能是对我们如何可视化数据影响最大的人。他的书《定量信息的视觉显示》(1983年)是那种永远改变你看图表方式的罕见文本之一。40多年来,它一直是全世界大学、新闻编辑部和设计工作室的绝对参考。 他的原则非常简单: 最大化数据-墨水比。 你图表的每个像素都必须传达数据。如果不传达数据,就删掉它。 不要装饰。 网格、3D渐变、阴影、装饰性边框……所有这些都是Tufte称之为图表垃圾的东西。分散数据注意力的视觉垃圾。 让数据自己说话。 如果你需要用200字的图例来解释你的图表,那图表就设计错了。 Tufte还发明了迷你图——那些能放在一行文字内的微型图表。图表不需要标题、坐标轴或图例来交流的想法。只需要数据。 我的图表很糟糕 我正在构建一个macOS菜单栏应用,用来监控我的Claude Max配额。应用的一部分显示一个迷你图——一个小小的线图——显示我消费配额的速度。 我的第一个版本有这些问题: Y轴固定在0到100% — 如果你的使用量在8%,图表就是一条贴在底部的平线。85%的空间什么都不显示。 在80%处有一条阈值线 — 任意的,没人要求,不传达任何有用信息。 显示当前%的浮动标签 — 冗余的:下面的卡片已经显示了确切数字。 “pp/min"作为单位 — 每分钟百分点?连我都不知道这意味着什么。 线下方的渐变填充 — 纯装饰。图表垃圾。 简单说:我在一个56像素高的图表中违反了Tufte的每一个原则。 技术:循环召唤专家 我没有试图自己修复它(显然我对此没有判断力),而是做了不同的事。我让Claude成为Tufte。 提示词: 像Edward Tufte审查这个图表一样行动。 分析VelocityCardView的SwiftUI代码。 识别你数据可视化原则的所有违规之处。 对于每个违规: 1. 违反了什么原则 2. 为什么这是个问题 3. 具体的处方(代码,不是哲学) 要无情。如果某些东西是图表垃圾,就说出来。 用PASS或FAIL评估。 关键是最后部分:PASS或FAIL。因为没有这个,LLM会给你温和的建议,告诉你"总体上还可以”。有了二元判决,它必须承诺。不能躲在"有改进空间"后面。 然后是循环: 应用Tufte的处方。 实施每个改变。 不要停止迭代,直到他给出认可。 四轮直到PASS 我第一轮没通过。第二轮也没有。第三轮也没有。 第1轮 — FAIL(6个处方): 自适应Y轴而不是固定的0-100 消除80%的阈值线(图表垃圾) 消除浮动标签(与下面的卡片冗余) 用简单词语替换"pp/min"(上升、下降、稳定) 将摘要行折叠为仅时间窗口+趋势 将迷你图高度从44提高到56点 我实施了6个。第二轮。 第2轮 — FAIL(1个处方): ...

2026年2月18日 · Fernando

Beads已死,Linear CLI长存

不到一个月前,我写了篇完整文章介绍如何在Claude Code中使用三层记忆系统:Linear负责战略、Beads负责战术、Tasks负责执行。构建了一个优雅的金字塔模型。 然而现实很骨感。 今天我正式退役Beads。这不是心血来潮,而是因为这个工具制造的麻烦已经超过了它解决的问题——它不再是工具,而是累赘。 Beads的初衷 对于没读过前文的读者,Beads是一个基于Git的issue跟踪器。作为Claude Code的插件,它将issues存储为代码库中的JSONL文件。理论上设计很精妙: Git持久化:issues保存在.beads/目录并与代码一起提交 依赖管理:支持issue间阻塞关系 离线工作:无需网络连接 LLM原生支持:直接读取文件,无需API配置 其核心价值是作为"本周计划"(Linear)和"当前任务"(Tasks)之间的战术衔接层。 故障始末 起初一切顺利,直到各种创意故障接踵而至。 恶魔守护进程 Beads依赖后台守护进程管理SQLite数据库并与Git同步。听起来合理?实际状况: 检测到数据库不匹配! 当前数据库属于其他代码库: 数据库记录库ID:d1f9ca0c 当前代码库ID:01eac8ea ⚠️ 严重警告:此错误可能导致同步时误删issues! 这个错误会在每次会话启动时出现。守护进程崩溃、同步失败,导致issues陷入量子态——既存在于本地SQLite又不存在于Git,反之亦然。 幽灵同步 bd sync本应同步Git远程仓库的issues,但经常失效: → 从远程拉取中... 错误:git pull执行失败:退出状态1 remote: 仓库不存在 fatal: 无法访问'https://git.frr.dev/frr/wuwei.git/' 当代码库配置多个remote时(这很常见),Beads可能选错远程仓库。若该仓库不存在或已更名,每次操作都会静默报错。最终issues停止同步,直到下次会话时数据全部消失才后知后觉。 认知损耗 每次Claude Code会话都这样开始: Claude读取Beads提示(通过hooks注入) 尝试启动守护进程 因数据库不匹配失败 Claude尝试bd sync 因远程仓库错误失败 你手动输入"忽略该错误" 终于可以开始工作 六个摩擦步骤消耗着上下文、时间和耐心。 局势转变 两件事让Beads从"带bug的实用工具"沦为"不必要的负担": 1. Tasks的成熟 当初设计三层架构时,Tasks功能简陋。现在已具备: 支持描述和元数据的TaskCreate 带依赖关系的TaskUpdate 查询功能TaskList/TaskGet 通过CLAUDE_CODE_TASK_LIST_ID实现跨会话持久化 简言之:Tasks现已实现Beads的所有会话内功能,且无需守护进程、SQLite或Git同步。 2. Linear CLI问世 原生的Linear管理控制台(MCP)往好了说也很糟糕——延迟高、稳定性差,总在关键时刻掉链子。 直接调用GraphQL API?理论上可行,直到你需要在issue描述中使用特殊字符: # 尝试1:使用bash字符串插值 # 结果:括号和箭头破坏JSON结构 # 尝试2:Python urllib方案 # 结果:因op read无法在Python环境执行报401错误 # 尝试3:默默流泪 # 结果:情绪宣泄但无实际产出 直到发现linear命令行工具: ...

2026年2月18日 · Fernando

Git工作树:如何让多个AI编程代理并行工作互不干扰

单一checkout的困境 我正在开发一个macOS菜单栏应用,有三个并行需求:消费趋势的走势图、原生通知和桌面小组件。这三个功能独立开发,都计划使用Claude Code实现。 问题在于:Claude Code需要在指定目录工作。而每个Git目录同一时间只能检出(checkout)一个分支。标准的git checkout就如同单车道环岛——每次只能通过一辆车。 如果想让三个功能并行开发,传统方案只有以下几种: 暂存乒乓:反复执行git stash保存更改→切换分支→git stash pop恢复→祈祷不要出现冲突…如此循环直到崩溃或退休,视哪个先到来。 克隆多个仓库:可行,但会产生三个完整的.git/目录副本、三个独立历史记录,以及三倍git fetch操作。资源浪费严重。 顺序开发:老老实实一个接一个开发。稳定可靠,但效率低得像手动执行归并排序。 这些方案都不理想。但Git其实自2015年起就内置了第四种解决方案——只是鲜为人知。 工作树(Worktree):现成的解决方案 Git工作树允许为同一仓库创建多个工作目录,无需克隆完整仓库。没有文件复制,没有魔法技巧。 类比说明:将Git仓库视作图书馆。原先你只有一张阅览桌,每次只能打开一本书。而工作树相当于增添多张阅览桌,每张桌子可打开不同的书,但所有书都来自同一个书库。 ~/code/miapp/ ← 主工作区(main分支) .git/ ← 核心书库(唯一存在) ~/code/miapp-sparkline/ ← 工作树1(feature/sparkline分支) .git ← 符号链接文件(指向主书库) ~/code/miapp-notificaciones/ ← 工作树2(feature/notifications分支) .git ← 另一符号链接 每个目录都是完整的检出副本,包含所有文件。你可以在一个目录编译代码,在另一个目录运行测试,同时让AI编程代理在第三个目录工作——真正并行不悖。 一行命令创建工作树 在项目主目录执行: git worktree add ../miapp-sparkline -b feature/sparkline git worktree add ../miapp-notificaciones -b feature/notifications 就这么简单。两个新目录立即创建完成,各自对应独立分支,共享同一Git数据库。无需克隆操作,无需配置远程仓库,无需复制历史记录。 共享与隔离机制 关键在于理解:所有工作树完全共享Git仓库数据——包括提交历史、分支、标签、远程配置和钩子脚本。当你在sparkline工作树提交代码,notifications工作树可即时看到此次提交,无需执行fetch操作。 但以下内容是独立的: 工作目录文件(每个工作树有自己的文件副本) 暂存区(各自独立的git add状态) HEAD指针(指向不同分支) 简言之:每个工作树的"当前工作状态"是私有的,其他所有仓库数据都是共享的。 与AI编程代理的协作流程 这才是工作树的精髓所在。通过工作树,你可以让多个AI编程代理真正并行工作: # 终端1:在sparkline分支运行Claude Code cd ~/code/miapp-sparkline claude # 终端2:在notifications分支运行Claude Code cd ~/code/miapp-notificaciones claude # 终端3:保持主分支运行 cd ~/code/miapp make run 每个Claude实例都在独立目录、独立分支下操作,拥有独立的.build/缓存目录。彼此互不干扰,无需竞争暂存区,更不需要反复执行stash操作。 ...

2026年2月16日 · Fernando