<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>王占伟</title><link>https://zhanwei.wang/zh/posts/</link><description>Recent content on 王占伟</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Fri, 08 May 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://zhanwei.wang/zh/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>我们给自己搭了个 AI Coding 工作台 —— 用 GitHub Actions 当沙箱</title><link>https://zhanwei.wang/zh/posts/ai-coding-workbench-on-gha/</link><pubDate>Fri, 08 May 2026 00:00:00 +0800</pubDate><guid>https://zhanwei.wang/zh/posts/ai-coding-workbench-on-gha/</guid><description>&lt;h2 id="起因"&gt;起因&lt;/h2&gt;
&lt;p&gt;我们团队这一两年用 AI 做编码工作的比例越来越高。最初是本地跑 Claude Code 之类的 CLI,后来发现一个实际问题:&lt;strong&gt;我每开一个 agent 任务,我的笔记本就被它占着&lt;/strong&gt;。Agent 在 build、跑测试、改文件、思考下一步;我同时想做点别的事,CPU 风扇就开始嚎叫。更别说想同时开五个 agent 跑五件不相关的事了。&lt;/p&gt;
&lt;p&gt;更难受的是协作。我跑了一个有意思的 agent 流程,想给同事看看进展,只能截图或者复制粘贴对话。同事想接手,我得把工作目录的状态打包给他。每次都像在邮件附件里来回传 word 文档。&lt;/p&gt;
&lt;p&gt;我们要的其实是一个&lt;strong&gt;远端、可分享、可扩展&lt;/strong&gt;的 AI Coding 工作环境——本地 IDE 仍然在,但 agent 长时间任务跑在某个&amp;quot;别处&amp;quot;,我们只是订阅它的进度。像 Anthropic Managed Agents 那种调一下 API、流式拿事件的体验,但跑在我们自己的基础设施上,用我们自己的密钥、连我们自己的内部工具。&lt;/p&gt;
&lt;p&gt;我们看了几条路:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自己搭一套 K8s 沙箱集群。&lt;/strong&gt; 技术上行得通。但镜像、网络、配额、安全加固、跨区,光这一摊够开一个团队做一年。我们才几个人。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 Replit / E2B / Modal 之类的现成沙箱。&lt;/strong&gt; 数据要走第三方,与 GitHub 的集成又要拼凑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;直接调 Anthropic / OpenAI 的现成 managed agents。&lt;/strong&gt; 不能用我们内部的 MCP server,不能用我们改过的 system prompt,不能用我们自己的工具策略,且数据走外。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最后我们绕回到一个看起来不大像主流答案的方向:&lt;strong&gt;用 GitHub Actions 的 runner 当沙箱。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;听起来有点奇怪。GHA 一向是 CI/CD 的工具,有 6 小时硬性超时,runner 是临时的,不像是给长任务设计的。但仔细想想,它已经具备我们要的几乎所有性质:&lt;/p&gt;</description></item><item><title>「全部测试通过」不等于「需求被满足」——为什么 AI 协作开发常常绿得很可疑</title><link>https://zhanwei.wang/zh/posts/green-but-wrong/</link><pubDate>Mon, 27 Apr 2026 00:00:00 +0800</pubDate><guid>https://zhanwei.wang/zh/posts/green-but-wrong/</guid><description>&lt;p&gt;很多团队把长任务交给 AI 协作执行后，会反复遇到同一种诡异结局：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;任务结束时，CI 一片绿。&lt;/li&gt;
&lt;li&gt;测试报告里展示着几百条新增 spec，覆盖率漂亮，行云流水。&lt;/li&gt;
&lt;li&gt;几天后，产品上线，第一个真用户走完一遍核心流程，触发了一个本该被覆盖却没被覆盖的 bug。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;回头查代码，会发现：那条所谓的&amp;quot;覆盖&amp;quot;测试，其实只是发了个状态码探针；那条所谓的&amp;quot;边界处理&amp;quot;，其实是 &lt;code&gt;console.warn&lt;/code&gt; 加 &lt;code&gt;return&lt;/code&gt;；那个所谓的&amp;quot;功能闭环&amp;quot;，缺了一张数据库表、一个 cookie、一个中间件——但这些缺失都没有让任何测试失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;绿色徽章和需求被满足之间，存在一道结构性的缝隙。&lt;/strong&gt; 这条缝隙是 AI 协作开发尤其容易掉进去的陷阱，但它的成因不是 AI 偷懒，而是大多数团队的工程信号根本没有设计来识别它。&lt;/p&gt;
&lt;p&gt;这篇文章想把这条缝隙拆开。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一绿色但错长什么样"&gt;一、「绿色但错」长什么样&lt;/h2&gt;
&lt;p&gt;抛开具体项目，几乎每一例都能归到下面五种形态里：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;软通过测试。&lt;/strong&gt; 测试不是断言契约该有的样子，而是接受一组宽到几乎不可能失败的可能值——&lt;code&gt;expect([200, 400, 403, 404, 409]).toContain(...)&lt;/code&gt;，或者 &lt;code&gt;if (!response.ok()) console.warn('未实现，先跳过')&lt;/code&gt;。这类测试存在的意义只剩下凑数：让&amp;quot;测试数量&amp;quot;指标好看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;状态码探针冒充端到端测试。&lt;/strong&gt; 名字叫 &lt;code&gt;J-checkout-flow.spec.ts&lt;/code&gt;，里头只发了一个 GET，断言 &lt;code&gt;status !== 405&lt;/code&gt;。&lt;strong&gt;测试名字承诺了一段用户旅程，内容只验证了路由没拼错。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;层级化报告。&lt;/strong&gt; 完工报告写「后端 156 项测试通过，前端 42 项测试通过」。这种描述方式根本无法回答&amp;quot;用户能不能完成 X 操作&amp;quot;这个真问题——你只能从中知道「测试存在，并且通过了」，无从知道它们究竟测了什么。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;藏在源码里的债务。&lt;/strong&gt; 把&amp;quot;未实现&amp;quot;写成代码注释、TODO、warn、文档段落——这些痕迹在 PR 评审里非常容易被忽略，并且永远不会出现在任何 issue 跟踪面板上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mock 与现实漂移。&lt;/strong&gt; 测试里的 mock 模拟的是接口去年的样子；接口真实形状已经迁移过一次。结果是 mock 在维护一个平行宇宙，测试通过，生产报错。&lt;/p&gt;
&lt;p&gt;这五种状态共有一个特征：&lt;strong&gt;它们都让 CI 显示绿色，但其中任何一种都不能保证用户旅程真的可走。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二为什么会反复发生不是因为不努力"&gt;二、为什么会反复发生（不是因为不努力）&lt;/h2&gt;
&lt;p&gt;如果你倾向于解释为「AI 偷懒了」「执行者不够尽职」，请先暂停一下。这种解释的问题在于它不可执行——你下次怎么办？让 AI 更努力？让人更细心？长任务下的&amp;quot;努力&amp;quot;和&amp;quot;细心&amp;quot;会在第几个小时之后开始衰减？&lt;/p&gt;
&lt;p&gt;更有用的是把它看成信号设计的问题。下面是几个能解释为什么这件事会&lt;strong&gt;反复&lt;/strong&gt;发生、而不只是偶发的机制：&lt;/p&gt;
&lt;h3 id="机制-1阻力梯度反向"&gt;机制 1：阻力梯度反向&lt;/h3&gt;
&lt;p&gt;写一条严格断言契约的端到端测试：你要先理解需求中预期的具体行为，构造完整流程，让它失败，再去定位修底层 bug。每一步都可能引出更深的修复，可能花几小时。&lt;/p&gt;</description></item><item><title>Claude Code Skill 的成本与性能优化：来自一次真实会话的 6 条通用原则</title><link>https://zhanwei.wang/zh/posts/skill-cost-optimization/</link><pubDate>Fri, 17 Apr 2026 00:00:00 +0800</pubDate><guid>https://zhanwei.wang/zh/posts/skill-cost-optimization/</guid><description>&lt;blockquote&gt;
&lt;p&gt;本文基于一次为期一周的 Claude Code Skill 优化实践，涉及 &lt;code&gt;prd-analysis&lt;/code&gt;、&lt;code&gt;system-design&lt;/code&gt;、&lt;code&gt;autoforge&lt;/code&gt; 三个生产级 Skill。所有数字来自真实的 JSONL session 文件（做了夸张系数修正，因为计费 API 的返回和实际扣费之间存在已知倍率）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="为什么要专门讲-skill-的成本"&gt;为什么要专门讲 Skill 的成本？&lt;/h2&gt;
&lt;p&gt;通用 LLM 省钱文章通常讲的是&amp;quot;上下文剪裁、cache 热身、模型降档&amp;quot;。这些对 Skill 也成立，但 Skill 执行环境有几个&lt;strong&gt;结构性差异&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;长会话 + 深调用栈&lt;/strong&gt;。Skill 内会派发多个子代理（subagent），每个子代理又可能调起自己的工具循环。一次派发 = 一个独立的对话上下文，&lt;strong&gt;子代理之间不共享 prompt cache&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主代理的 context 一旦被撑大就全程 cache_read&lt;/strong&gt;。Skill 会话常有 15–20 个主代理轮次；任何文件一旦进了主代理 context，就会在&lt;strong&gt;每一轮&lt;/strong&gt;以 cache_read 计费。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;档位由谁决定&lt;/strong&gt;含糊不清。&lt;code&gt;subagent_type&lt;/code&gt; 是内置代理（如 &lt;code&gt;Explore&lt;/code&gt;）会强制某个档位，而 &lt;code&gt;general-purpose&lt;/code&gt; + 显式 &lt;code&gt;model&lt;/code&gt; 才由 Skill 控制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出 token 严重被低估&lt;/strong&gt;。Sonnet 的 output 价格是 cache_read 的 &lt;strong&gt;50×&lt;/strong&gt;；Opus 是 &lt;strong&gt;50×&lt;/strong&gt;。Skill 作者凭直觉会优化&amp;quot;少读文件&amp;quot;，却忽略&amp;quot;少写 prompt&amp;quot;这条更大的杠杆。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以下 6 条都是从实测中提炼的，改 Skill 文件就能生效。&lt;/p&gt;</description></item><item><title>当文档不再腐烂：从 Karpathy 的 LLM Wiki 到软件工程文档管理</title><link>https://zhanwei.wang/zh/posts/when-documentation-stops-rotting/</link><pubDate>Thu, 09 Apr 2026 00:00:00 +0800</pubDate><guid>https://zhanwei.wang/zh/posts/when-documentation-stops-rotting/</guid><description>&lt;blockquote&gt;
&lt;p&gt;人负责写原始文档和做决策，LLM 负责综合、更新和一致性检查。文档不再是写完即腐烂的静态产物——它变成了一个由 LLM 持续维护的活知识库。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="一个古老的问题"&gt;一个古老的问题&lt;/h2&gt;
&lt;p&gt;每个软件工程师都经历过这样的场景：你接手一个项目，翻开文档，发现 README 还在描述半年前删掉的模块，API 文档里的字段和代码完全对不上，架构图画的是两个版本前的设计。你问同事，同事说&amp;quot;别看文档，直接看代码吧&amp;quot;。&lt;/p&gt;
&lt;p&gt;文档腐烂（documentation decay）不是因为工程师不想写文档，而是因为维护文档的成本太高。写一份设计文档可能花两个小时，但之后每次代码变更都需要回来同步更新 —— 检查交叉引用是否还成立、术语是否一致、边界条件是否还对 —— 这些琐碎的记账工作让人疲惫，最终被放弃。&lt;/p&gt;
&lt;h2 id="karpathy-的想法知识编译而非知识检索"&gt;Karpathy 的想法：知识编译而非知识检索&lt;/h2&gt;
&lt;p&gt;2025 年，Andrej Karpathy 提出了一个关于 LLM 与知识管理的想法。他的主张出奇地简单：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要让 LLM 每次都从原始文档中重新发现模式，而应该让它构建和维护一个结构化的知识库，让知识持续积累。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就是 LLM Wiki 模式。它和传统的 RAG（检索增强生成）的区别在于 —— RAG 每次查询都强迫 LLM 从零开始重建理解，而 Wiki 模式将综合（synthesis）本身当作一等公民的产物。交叉引用已经建立，矛盾已经被标记，概念之间的关系已经被梳理——知识在持续积累，而不是每次从头来过。&lt;/p&gt;
&lt;p&gt;Karpathy 勾勒了一个三层架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;原始来源（Raw Sources）&lt;/strong&gt;：人类策划的不可变文档 —— 论文、文章、笔记。LLM 永远不碰这一层。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wiki&lt;/strong&gt;：LLM 生成和维护的 Markdown 页面 —— 摘要、实体、概念、对比。LLM 完全拥有这一层的结构和内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema&lt;/strong&gt;：定义 wiki 结构、命名约定和工作流的配置文件，让 LLM 成为一个有纪律的维护者而不是一个通用聊天机器人。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及三个操作：&lt;strong&gt;Ingest&lt;/strong&gt;（摄入新来源并更新 wiki）、&lt;strong&gt;Query&lt;/strong&gt;（查询并将有价值的发现回写 wiki）、&lt;strong&gt;Lint&lt;/strong&gt;（健康检查 —— 找矛盾、找孤立页面、找缺失引用）。&lt;/p&gt;
&lt;p&gt;他还提了一个有意思的分工：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;人的工作是策划来源、指导分析、问好的问题。LLM 的工作是其他一切。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个&amp;quot;其他一切&amp;quot;，恰好是让人维护不下去的那些事 —— 更新摘要、维护交叉引用、检查一致性。这些工作对人来说是乏味的苦差事，对 LLM 来说却是可靠且不知疲倦的日常。&lt;/p&gt;</description></item><item><title>Vibe Coding 时代的前端驱动开发</title><link>https://zhanwei.wang/zh/posts/vibe-coding-frontend-driven-development/</link><pubDate>Mon, 09 Mar 2026 10:00:00 +0800</pubDate><guid>https://zhanwei.wang/zh/posts/vibe-coding-frontend-driven-development/</guid><description>&lt;p&gt;AI 已经成了日常开发工具。问题是，我们的工作方式跟上了吗？&lt;/p&gt;
&lt;h2 id="三种开发模式的进化"&gt;三种开发模式的进化&lt;/h2&gt;
&lt;p&gt;在讨论前端驱动开发之前，我想先说说三种很不一样的开发模式，以及它们是怎么演进的：&lt;/p&gt;
&lt;h3 id="1-后端驱动开发传统方式"&gt;1. 后端驱动开发（传统方式）&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;产品需求 → 后端设计 API（1-2周）→ 前端开发页面（2周）→ 联调
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;说个真事。&lt;/p&gt;
&lt;p&gt;去年我参与的电商后台管理系统，团队按照这个模式来。当前端拿到 API 文档时，已经是开发周期的第三周。我花两周时间按照文档实现了订单管理模块。上线展示给产品经理时，她皱起眉头：「为什么订单列表不能按照状态筛选？用户肯定需要这个。」&lt;/p&gt;
&lt;p&gt;返工，两天的工作打水漂。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;问题出在哪&lt;/strong&gt;：产品需求完全成型、后端 API 定稿之后，前端才能真正看到交互流程。此时发现的问题，返工代价极高。而且前端是「被动接收者」，无法提前验证需求的合理性。&lt;/p&gt;
&lt;h3 id="2-api-驱动开发理想模式"&gt;2. API 驱动开发（理想模式）&lt;/h3&gt;
&lt;p&gt;很多团队推崇的做法：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;产品需求 → 前后端共同定义 API 约定 → 前后端同时开发 → 联调
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这在理论上很完美。前后端基于同一份 API 契约各自开发，分工明确，进度可以并行。问题是？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现实中的问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API 定义很难一次到位&lt;/strong&gt;。产品经理往往说不清楚交互细节，后端也无法完全预见所有的边界情况。「先定义 API」听起来理性，实际上这个定义过程本身很容易出错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;没有可见化的交互反馈&lt;/strong&gt;。前端按照 API 约定写代码，但产品经理看不到「动」的界面。等联调时才发现「啊，这个功能应该这样用」，又要改。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;后端容易过度设计&lt;/strong&gt;。没有看到真实的 UI 界面需求，后端可能会设计出过于复杂的 API 结构来「应对所有可能」。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个模式对于小团队或者需求不明确的项目，往往流于形式。&lt;/p&gt;
&lt;h3 id="3-前端驱动开发为什么在-vibe-coding-时代更有优势"&gt;3. 前端驱动开发（为什么在 Vibe Coding 时代更有优势）&lt;/h3&gt;
&lt;p&gt;前端驱动的概念并不新，但在 AI 工具的加持下，它的成本模型完全不一样了。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;产品需求 → 前端快速交互原型（用Mock数据）
 ↓
产品/客户实时反馈 &amp;amp; 确认流程
 ↓
前端迭代交互逻辑（基于 AI 辅助）
 ↓
双方确认 API 约定（基于已验证的交互）
 ↓
后端实现 API（数据结构已确认）
 ↓
联调（几乎没有结构问题）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为什么现在更可行？前端用 Mock 数据迅速把「抽象的需求」变成「可点击的原型」。产品经理、客户能看到真实的交互流程，给出具体的反馈。这个反馈再驱动 API 设计。&lt;/p&gt;</description></item></channel></rss>