<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>给自己的备忘录</title><description>给自己的备忘录：曾凯的个人网站。每做完一件事就记一条备忘——踩过的坑、验证过的判断，写的多是 AI 编码工具、iOS 工程实践与个人产品。</description><link>https://infingrow.asia/</link><language>zh-CN</language><item><title>发刊词</title><link>https://infingrow.asia/blog/memo-to-myself/</link><guid isPermaLink="true">https://infingrow.asia/blog/memo-to-myself/</guid><description>从《101 忠狗》里那个每次失败都拿出录音笔反思的女反派说起——哪有那么多主角，所以这是：给自己的备忘录。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;小时候经常看《101 忠狗》，现在故事情节基本都忘了，但女反派却令人印象深刻——每次失败，就会拿出录音笔记录反思：「给自己的备忘录！……」&lt;/p&gt;
&lt;p&gt;虽然影视剧中主角们常以爱与友情取胜，但哪有那么多主角，所以这是：给自己的备忘录。&lt;/p&gt;
</content:encoded></item><item><title>十年之后，重新开一个博客</title><link>https://infingrow.asia/blog/hello-again/</link><guid isPermaLink="true">https://infingrow.asia/blog/hello-again/</guid><description>2015 年我在 GitHub Pages 上搭过一个博客，写了 8 篇备忘就弃更了。十年之后重新开站，这篇交代它从哪来、打算写什么。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2015 年 12 月 31 日，我在 GitHub Pages 上部署了自己的第一个博客。Hexo 加 NexT 主题，站名「Hero For Myself」，副标题「代码打败时间」，站点描述里贴了康德那句「头顶的星空与心中的道德律」。&lt;/p&gt;
&lt;p&gt;那个博客一共写了 8 篇：Reveal 视图调试工具的两句话介绍、Git 命令备忘、Hexo 建站命令备忘、一份 Charles 抓包的外链收藏，还有几篇只有标题没有正文。最长的一篇不到 700 字符。最后一次部署停在 2016 年 4 月 15 日，之后再没有更新。&lt;/p&gt;
&lt;p&gt;现在回看，那 8 篇与其说是文章，不如说是一个刚入行一年多的 iOS 工程师随手放在公开位置的便签。它们不值得迁移到新站，就让它们留在 2016 年。但站名和副标题我记了十年。&lt;/p&gt;
&lt;h2&gt;这十年&lt;/h2&gt;
&lt;p&gt;弃更博客的十年里，事情大致是这样走的。&lt;/p&gt;
&lt;p&gt;2014 年从武汉工程大学网络工程专业毕业，第一份工作做智慧水务平台，Web 端和 iOS 端都写，远程抄表、网上缴费、报表统计。2015 年到 2018 年在一家创业公司做 Feel，一个运动健康类 App 的 iOS 端，支撑到千万级注册用户的规模，也是在那里开始搭 Fastlane 自动化打包这类工程效率的东西。&lt;/p&gt;
&lt;p&gt;2018 年 6 月加入字节跳动，之后长期在今日头条做 iOS 客户端的品质和研发效率：卡死治理、自动化测试的数据消费、双列信息流的性能专项。2024 年起工作重心转向 AI 应用方向——AI 编程助手、CLI 编码工具、多模型网关，到最近的团队 AI 知识库机制。&lt;/p&gt;
&lt;p&gt;业余时间一直在做小工具。2017 年发布过一个解决 Storyboard 跨文件跳转的库到 CocoaPods；这几年做的是 Raycast 扩展（提示词管理、上下文聚合、文件入口）和自己的 iOS / macOS 应用（阅读听书、速记、专注计时、语音输入桥接）。&lt;/p&gt;
&lt;p&gt;GitHub 的 fork 列表把这条路径记录得比我自己的记忆更诚实：早年 fork 的是 AFNetworking、YYKit、FLEX 这类 iOS 库；2022 年之后变成效率工具和脚本；再往后是 llm.c、Agent 框架和各种 AI 项目。兴趣的迁移有迹可循，没有哪一步是突然发生的。&lt;/p&gt;
&lt;h2&gt;为什么重新开站&lt;/h2&gt;
&lt;p&gt;直接的原因有三个。&lt;/p&gt;
&lt;p&gt;第一，存量到了值得整理的程度。这几年在公司内网写了二十来篇文章和课程，从卡死治理写到 AI 编码实践；本地还有一堆方法论笔记——调研怎么做、Skill 怎么设计、评测怎么建。它们散在内网、笔记库和聊天记录里，只有整理成对外能读的版本，才算真正属于我自己。&lt;/p&gt;
&lt;p&gt;第二，写作本身是工作方法。Leslie Lamport 有句话我贴在笔记里很久：「If you&apos;re thinking without writing, you only think you&apos;re thinking.」这几年与 AI 协作得越深，越确认表达质量就是产出质量。这个判断最早出现在 2022 年的一篇内网文章里——四年过去，它比当时更成立，以后值得单独写一篇。博客是这个练习的公开版本。&lt;/p&gt;
&lt;p&gt;第三，AI 把生成内容的成本压到了接近零，粗制滥造的东西只会越来越多。我不想加入其中，想做的是反面：数量少一点，每一篇都基于真实做过的事，数字有出处，边界写清楚。&lt;/p&gt;
&lt;h2&gt;这个站有什么&lt;/h2&gt;
&lt;p&gt;三个部分。&lt;a href=&quot;https://infingrow.asia/projects&quot;&gt;作品&lt;/a&gt;：这些年做的个人产品和工具，包括开源的和自用的，每个都写清楚定位、取舍和状态。&lt;a href=&quot;https://infingrow.asia/blog&quot;&gt;文章&lt;/a&gt;：工作项目的对外复盘（脱敏后重写）、方法论笔记和个人工具的排障记录。&lt;a href=&quot;https://infingrow.asia/notes&quot;&gt;想法&lt;/a&gt;：一句话级别的短想法，从日常笔记里挑出来的。&lt;/p&gt;
&lt;p&gt;开站这批文章是同一天发布的：它们是过去几年存量的一次集中整理，不是一天写出来的。初稿发表过内网版本的，重写时在文首注明了原始年份。之后就按写完一篇发一篇的节奏来。&lt;/p&gt;
&lt;p&gt;「代码打败时间」这个副标题沿用了下来，理解和十年前不一样了。当年大概是想说，写下的代码能替自己留下点什么。今天 AI 让代码本身越来越便宜，真正能穿过时间的，是被验证过的判断和可以复用的方法。代码只是它们的一种载体，文章是另一种。&lt;/p&gt;
&lt;p&gt;站名换成了「给自己的备忘录」，来历写在&lt;a href=&quot;https://infingrow.asia/blog/memo-to-myself&quot;&gt;发刊词&lt;/a&gt;里——当年写的那 8 篇本来就是备忘，这次索性让它名副其实。人还是这个人，工具已经全部换代，希望这次比 8 篇更远一些。&lt;/p&gt;
</content:encoded></item><item><title>按下 Fn 就说话：一个 macOS 语音输入工具的三个坑</title><link>https://infingrow.asia/blog/fn-voice-input-bridge/</link><guid isPermaLink="true">https://infingrow.asia/blog/fn-voice-input-bridge/</guid><description>把「切输入法、双击 Option」压缩成一个 Fn 键：CGEventTap 静默禁用、输入法假切换、修饰键状态残留，三个深水区问题的排障记录。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;一个键换掉一串动作&lt;/h2&gt;
&lt;p&gt;我日常重度使用豆包输入法的语音输入（豆包是字节跳动的 AI 助手产品，配套的输入法自带语音识别）。它很好用，但启动路径有点长：先把系统输入法切到豆包，再双击 Option 启动语音识别；说完之后，还要把输入法切回原来常用的那个。这套动作高频重复，每次都会打断手上的节奏。&lt;/p&gt;
&lt;p&gt;动作本身不复杂，烦在频次。于是我给自己做了一个 macOS 菜单栏工具，把整套动作压缩成一个键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按一下 Fn：切到豆包输入法，模拟双击左 Option，语音输入开始；&lt;/li&gt;
&lt;li&gt;再按一下 Fn：停止语音输入，恢复最近一次使用的非豆包输入源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工具常驻菜单栏，没有主窗口，日常的全部交互就是那一个 Fn 键。&lt;/p&gt;
&lt;p&gt;实现上有两个不起眼但关键的细节。一是 Fn 键的识别：按下 Fn 时，系统产生的是一个 keycode 为 179 的 keyDown 事件，需要在事件流里把它识别为 Fn 本身。二是时序：Fn 触发后要延迟 0.2 秒再模拟 Option 双击——立刻双击也能启动语音输入，但识别准确度会明显下降，推测是语音引擎还没就绪。&lt;/p&gt;
&lt;p&gt;最初版本是用 Hammerspoon（macOS 上用 Lua 写自动化脚本的工具）实现的，很快跑通了主流程。日常用了一段时间后，我决定用 Swift 重写成原生菜单栏 App：脚本方案的事件处理稳定性和启动依赖都不可控，一个打算长期日用的工具，值得原生化。重写过程里踩了三类坑，都在 macOS 系统编程的深水区，逐个记录。&lt;/p&gt;
&lt;h2&gt;坑一：CGEventTap 回调被系统静默禁用&lt;/h2&gt;
&lt;p&gt;症状很有迷惑性：工具闲置一段时间后，第一次按 Fn 没反应，再按一下又恢复正常。没有崩溃，也没有任何日志。&lt;/p&gt;
&lt;p&gt;根因是 CGEventTap 的一个保护机制：tap 的回调如果在约 1 秒内没有返回，系统会认定它出了问题，直接把这个 tap 禁用，后续事件不再经过它。这个机制本身合理——事件 tap 处在系统输入链路上，一个卡住的回调会拖累所有应用的按键响应——但「禁用」这个动作完全静默，不主动检查，就永远不会发现 tap 已经失效。&lt;/p&gt;
&lt;p&gt;我的回调里恰好有阻塞调用：用 TIS（Text Input Source Services）接口读取当前输入法。这类接口涉及跨进程通信，偶尔会慢，一慢就可能超过约 1 秒的限制。闲置后更容易复现，和 App Nap 有关：macOS 会把闲置进程降频调度，回调超时的概率随之变大。&lt;/p&gt;
&lt;p&gt;最终的解法是四条一起上：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EventTap 挪到独立线程，与主线程隔离，避免互相拖累；&lt;/li&gt;
&lt;li&gt;加 watchdog，定时检查 tap 的启用状态，发现被禁用就重建——销毁旧的 tap，重新创建并挂回事件链路，代价很小；&lt;/li&gt;
&lt;li&gt;进程禁用 App Nap，避免闲置时被降频；&lt;/li&gt;
&lt;li&gt;回调内不做任何阻塞调用——回调只做最轻量的判断，重活全部抛出去异步执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四条各有分工：独立线程和零阻塞回调降低超时的概率，禁用 App Nap 处理闲置场景，watchdog 负责兜底——就算前面全部失效，tap 也会在下一次检查时被重建拉起。&lt;/p&gt;
&lt;h2&gt;坑二：输入法「假切换」&lt;/h2&gt;
&lt;p&gt;第二个坑出在输入法切换上。在一些 Electron 应用里，调用 TIS 接口切换输入法之后，菜单栏的输入法图标已经变了，但继续打字，实际生效的还是旧输入法：系统层面切换成功，前台应用没有跟上。&lt;/p&gt;
&lt;p&gt;解法是对前台 App 做一次焦点刷新，强制它重新感知输入法的变化。但焦点刷新会带来可见的闪动，不适合无差别启用，所以做成了配置白名单：只对已知存在这个问题的 Electron 应用开启，其余应用维持默认行为。&lt;/p&gt;
&lt;p&gt;这个坑又牵出一个相关的问题：怎么可靠地判断「前台 App」。最直觉的 &lt;code&gt;NSWorkspace.frontmostApplication&lt;/code&gt; 并不可靠——Raycast 这类浮窗应用弹出时，它会给出误判。最终采用三层判定顺序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先用 AX（辅助功能）接口取系统焦点所在的应用；&lt;/li&gt;
&lt;li&gt;取不到，就找最上层的可见窗口，反查它所属的应用；&lt;/li&gt;
&lt;li&gt;都失败，才用 &lt;code&gt;frontmostApplication&lt;/code&gt; 兜底。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;顺带一提，AX 接口和 CGEventTap 都依赖系统的辅助功能授权，这是这类键盘工具绕不开的前置条件。&lt;/p&gt;
&lt;h2&gt;坑三：模拟修饰键的状态残留&lt;/h2&gt;
&lt;p&gt;第三类坑出在「模拟双击 Option」这个看似最简单的环节。双击 Option 的合成序列是四个事件：按下、抬起、按下、抬起。事件本身不难发，问题全部出在修饰键的状态管理上。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件源要用 &lt;code&gt;.hidSystemState&lt;/code&gt; 创建，让合成事件携带的修饰键状态和系统实际状态保持一致；&lt;/li&gt;
&lt;li&gt;释放修饰键时，只移除对应的 flag 位。图省事把 flags 整个清零的话，用户真实按着的其他修饰键也会被一起清掉——比如用户恰好按着 Shift，合成事件一结束，这个 Shift 就「失灵」了；&lt;/li&gt;
&lt;li&gt;整个流程结束后检查残留位，发现还有修饰键停留在按下状态，就补发抬起事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一节还有一个结论是「不修」。录屏软件的按键可视化一直显示我合成的 Option 处于按住状态，看起来像抬起事件丢了。逐项核对后确认：事件的收发都正确，行为本身没有问题，是录屏软件对合成事件的显示有偏差。这条我标记为不修。排障时把「真 bug」和「观测工具的假象」区分开，本身就是一课——观测手段也是软件，也有自己的 bug。&lt;/p&gt;
&lt;h2&gt;稳定性比功能费功夫&lt;/h2&gt;
&lt;p&gt;这个工具的功能一句话就能说完，但回头看代码，真正花功夫的部分几乎都和功能无关：独立线程、watchdog、App Nap、状态残留检查、白名单降级。原因在于这类常驻工具的验收标准是「几个月无感常驻」——不用打开界面，不用重启，最好想不起它的存在。任何一次「按了没反应」都在消耗信任，次数多了，我自己都会退回手动切输入法的老路。&lt;/p&gt;
&lt;p&gt;这也解释了为什么这些坑大多要在常驻运行中才暴露：短时间试用什么问题都没有，跑得久了，tap 被禁用、修饰键残留这类低概率事件才会一一浮现。&lt;/p&gt;
&lt;p&gt;从 Hammerspoon 到原生重写的决定，逻辑也是同一条：脚本方案验证想法很快，但要做到「无感常驻」，事件处理和启动依赖都得握在自己手里。功能很快就能写完，稳定是在日常使用里一点点修出来的。&lt;/p&gt;
&lt;p&gt;这个工具的定位、取舍和当前状态，写在它的&lt;a href=&quot;https://infingrow.asia/projects/doubao-voice-bridge&quot;&gt;项目页&lt;/a&gt;。&lt;/p&gt;
</content:encoded></item><item><title>把丢帧时长占比降下来：双列信息流的流畅度优化</title><link>https://infingrow.asia/blog/htr-feed-smoothness/</link><guid isPermaLink="true">https://infingrow.asia/blog/htr-feed-smoothness/</guid><description>以 HTR 为核心指标建评估、找劣化、做优化，再把问题左移到合码前：记录今日头条双列信息流性能专项的方法与结果。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;2023 年我主导了今日头条双列信息流的性能专项。「双列」指类似小红书样式的双列卡片流：一屏两列、卡片高度参差，当时是头条站内仅次于推荐 feed 的高流量场景。高流量意味着体验问题会被成倍放大，也意味着优化的回报足够高，值得专项投入。&lt;/p&gt;
&lt;p&gt;这篇主要写方法：怎么建评估、怎么找问题、优化了什么，以及怎么让优化成果不随时间退化。&lt;/p&gt;
&lt;h2&gt;指标：为什么是 HTR&lt;/h2&gt;
&lt;p&gt;专项的第一件事是选指标。指标一旦定下，就是后面所有优化的验收口径，值得先花时间想清楚。我们的流畅度核心指标是 HTR（Hitch Time Ratio）：单位时间内丢帧时长的占比。&lt;/p&gt;
&lt;p&gt;先解释 hitch。屏幕以 60Hz 刷新时，一帧的预算约 16.7ms；一帧没能按时提交，延后的这段时间就是这一帧的 hitch time。把一段时间内所有 hitch time 累加，除以这段时间的总时长，就得到 HTR。&lt;/p&gt;
&lt;p&gt;比起「掉帧次数」这类计数型指标，HTR 更贴近用户体感，因为它同时反映丢帧的频率和严重程度：轻微晚到一点和整段卡住，计数上可能都算一次掉帧，体感却完全不同；HTR 按时长累计，严重卡顿在指标上的权重自然更大。平均帧率也有类似的盲区——平均值会把局部的严重卡顿摊平，滑动大部分时间很顺、偶尔明显卡一下的场景，平均帧率可能依然好看。Apple 在讲渲染性能的官方材料里，用的也是同类口径。&lt;/p&gt;
&lt;h2&gt;先建评估，再动手优化&lt;/h2&gt;
&lt;p&gt;单点优化谁都能做几个，难的是持续回答两个问题：现在卡在哪，改完有没有变好。所以专项先花力气建评估，线下线上各一套。&lt;/p&gt;
&lt;p&gt;线下用 Instruments 配合 &lt;code&gt;os_signpost&lt;/code&gt; 打点：把滑动过程中的耗时任务全部标出来，导出后画成甘特图，主线程、子线程、关键任务、白屏区间用不同颜色区分，关键路径一眼可见。打点本身没有多少技术含量，关键是覆盖要全：主线程之外，子线程的任务也要标出来，不少卡顿的根源在子线程把资源占满，只盯主线程找不到原因。这张图对协作的价值超出预期——「感觉卡」没法讨论，「看见哪一段在卡」才能分工。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://infingrow.asia/images/htr-gantt-schematic.svg&quot; alt=&quot;甘特图示意：主线程与子线程的任务沿时间轴排布，超出帧预算的一段被标记为 hitch&quot; /&gt;&lt;/p&gt;
&lt;p&gt;线上建了五个核心指标的大盘，覆盖流畅度、首刷耗时（进入场景到第一屏内容展示完成的耗时）等，再配两个机制：实验劣化报警，任何一个实验（A/B 测试）导致指标劣化都会触发报警；劣化归因，指标退化时能定位到引入退化的版本或实验。报警的意义在于把发现劣化的时机从「版本发布之后」提前到「实验开启之后」，问题还没放量就能被看到。&lt;/p&gt;
&lt;p&gt;评估建好之后，找优化空间就有了两个稳定的方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;和自己比：版本之间、实验组之间做劣化归因，找出每一个退化点。已经退化过的地方，往往就是最容易拿回来的空间。&lt;/li&gt;
&lt;li&gt;和别人比：跨场景对比指标，双列和站内的推荐 feed 比，也和竞品的同类场景比。差距摆在那里，差多少，优化空间就至少有多少；竞品能做到的水平，也证明了在同类设备和系统上技术可达，这类对比最能说服团队投入。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;优化了什么&lt;/h2&gt;
&lt;p&gt;具体优化项沿「请求发出到画面渲染」的链路排下来，每项说一句原理。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;feed 请求时机提前：把首刷请求提前到启动更早的阶段发出，让网络耗时和启动阶段的其他初始化并行，等界面准备渲染时，数据大概率已经回来了。&lt;/li&gt;
&lt;li&gt;减少端内请求开销：请求真正发到网络之前，要在端内经过序列化、拦截器等环节，这部分开销做了裁剪。&lt;/li&gt;
&lt;li&gt;Cell 高度计算优化：双列卡片高度参差，高度在布局和滚动过程中会被反复询问；加高度缓存，避免同一张卡片重复计算。&lt;/li&gt;
&lt;li&gt;RunLoop 分周期刷新：把一次刷新要做的事拆到多个 RunLoop 周期里执行，避免单帧内堆积过多任务导致该帧超时。总的工作量没有变少，但摊薄到多帧之后，每一帧都能按时提交。&lt;/li&gt;
&lt;li&gt;多代理事件分发优化：滚动时 &lt;code&gt;scrollViewDidScroll&lt;/code&gt; 这类回调要分发给多个代理，滚动是高频事件，分发本身的开销积少成多，值得优化。&lt;/li&gt;
&lt;li&gt;首刷杂项：段压缩预载、配置系统初始化提前等。思路一致：把首刷路径上的等待，挪到更早、更空闲的阶段去做。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果用两个口径衡量：首刷耗时累计优化约 150ms；HTR 优化约 50%。&lt;/p&gt;
&lt;h2&gt;问题左移：让劣化在合码前被拦下&lt;/h2&gt;
&lt;p&gt;优化做完只是一半。信息流场景需求密度高，新代码每天都在合入，没有防线，指标会在之后的版本里悄悄退回去。所以专项的另一半力气花在「问题左移」上：把发现问题的时机往开发流程的更早阶段移，能在合码前拦住的，就不拖到线上再查。具体做了三件事。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;录制回放替代人工回归：把双列的回归用例用录制回放全量覆盖，同样的回归范围，测试提效 4-6 倍，人从重复操作中解放出来。&lt;/li&gt;
&lt;li&gt;合码前性能拦截：在 MR（merge request）阶段自动跑性能对比，严重的流畅度劣化直接拦截，不允许合入。问题在合码前修，成本远低于上线报警之后再回头查。&lt;/li&gt;
&lt;li&gt;自研基于 signpost 的自动化测试工具：耗时变化可以精确到毫秒级，直接定位到具体任务。性能拦截回答「有没有劣化」，这个工具回答「劣化在哪」。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;回头看&lt;/h2&gt;
&lt;p&gt;单点优化技巧，在各种技术分享里都能找到类似的；这个专项里真正难、也真正值钱的，是先建立「能持续发现问题」的评估。没有大盘和归因，优化做完的那一刻就是指标的顶点，随后慢慢退化回去，过一段时间一切重来。专项总有结束的一天，投入的人力会撤走，能留下来的是大盘、报警、归因和合码前拦截这些机制——指标最终能不能守住，靠的是它们。&lt;/p&gt;
&lt;p&gt;另一个印象深的点是可视化的杠杆作用。甘特图把「感觉卡」变成「看见哪一段在卡」，问题从主观体感变成客观事实，和团队对齐问题的效率完全不同。方法本身没有任何新意——打点、画图而已——但它决定了后面所有优化工作的沟通成本。&lt;/p&gt;
</content:encoded></item><item><title>AI 辅助的 iOS 到 KMP 代码迁移实践</title><link>https://infingrow.asia/blog/kmp-migration-with-ai/</link><guid isPermaLink="true">https://infingrow.asia/blog/kmp-migration-with-ai/</guid><description>把 Objective-C 代码迁移到 Kotlin Multiplatform，AI 转写能做到什么程度？一次真实模块迁移的流程、迭代记录与适用边界。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;团队在推进底部 Tab 功能的 KMP 化——KMP（Kotlin Multiplatform）是把业务逻辑用 Kotlin 写一遍、双端共用的跨平台方案，存量的 Objective-C 代码需要转写成 Kotlin。手工逐行翻译既耗时又容易出错，我们借这个机会验证了一件事：AI 辅助的代码迁移，能做到什么程度。&lt;/p&gt;
&lt;p&gt;这次实践要回答三个问题：AI 转写的采纳率能到多少；哪些类型的代码适合交给 AI 转写；人和 AI 各自该干什么。&lt;/p&gt;
&lt;h2&gt;工具前提&lt;/h2&gt;
&lt;p&gt;转写工作跑在团队的 AI 编码启动器上。它本身不是模型，是一层工程化封装：在当前代码仓库里一键接入 CLI Coding Agent（Claude Code 这类），同时把团队知识库挂载进工作目录。具体做了四件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;知识库挂载：把工程规则文档和平台接口文档软链到工程目录，Agent 对话时自动带上工程规范。&lt;/li&gt;
&lt;li&gt;一键启动：自动检测并启动 CLI Agent，不需要每个人手动配环境。&lt;/li&gt;
&lt;li&gt;按平台过滤：文档可以按 iOS / Android / KMP 过滤，减少无关文档体积，提高检索命中率。&lt;/li&gt;
&lt;li&gt;缓存加速：依赖环境装在缓存目录，没有变化就跳过安装。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这层封装决定了后面所有做法的可行性：转写规则、平台接口文档、进度追踪都以文件形式放在 Agent 随手可及的位置，而不是靠人在对话里反复粘贴。&lt;/p&gt;
&lt;h2&gt;转写前的准备&lt;/h2&gt;
&lt;p&gt;准备阶段的投入直接决定转写质量的上限。三件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;模块化拆分&lt;/strong&gt;。先把要迁移的代码内聚成独立的 Objective-C 类，再逐一转写。让 AI 每次聚焦一个小模块，比一次投喂海量代码的准确率高得多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基建文档先行&lt;/strong&gt;。把 KMP 侧已有的能力——网络请求、数据模型、工具类、平台接口——以文档形式放进知识库。AI 能复用这些已有模式，而不是自己重新发明一套。后面的迭代记录会说明，这一条没做好是第一轮返工的主要原因。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;纯逻辑代码优先&lt;/strong&gt;。业务逻辑、数据处理这类与平台耦合少的代码最容易转成 Kotlin，复用效果也最好。UI 代码先放着。&lt;/p&gt;
&lt;h2&gt;转写流程&lt;/h2&gt;
&lt;p&gt;单个模块的转写流程固定下来是这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OC 源文件（.h + .m 配对输入）
  → 加载知识库（自动拉取 KMP 规范文档与目标仓库）
  → 强制学习平台接口文档
  → 按目标范式执行翻译（StateHolder / ViewModel / 普通类）
  → 产出 Kotlin 代码，更新进度文件
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两个设计值得说明。&lt;/p&gt;
&lt;p&gt;一是&lt;strong&gt;强制使用 SPI&lt;/strong&gt;。KMP 代码调用平台能力（KV 存储、日志、埋点、AB 实验）必须走统一的 SPI 接口（Service Provider Interface，平台能力的抽象层），不允许直接触碰平台 API。我们把「先读 SPI 文档」写成流程里的强制步骤，跳过这一步的转写结果几乎必然要返工。&lt;/p&gt;
&lt;p&gt;二是&lt;strong&gt;进度文件&lt;/strong&gt;。用一个 Markdown 文件记录每个模块的翻译和审查状态。多轮会话、多人协作时，Agent 和人看同一份进度，不依赖谁的记忆。&lt;/p&gt;
&lt;h2&gt;迭代审查&lt;/h2&gt;
&lt;p&gt;转写完成不等于结束，进入「自动审查 + 人工审查 → AI 修复」的循环：先跑专用的审查命令让 AI 对照编码规范自查，再由开发者人工审查标注问题，问题反馈给 AI 修复，然后再审，直到通过。&lt;/p&gt;
&lt;p&gt;以第一个完成迁移的核心类（底部 Tab 红点逻辑的管理类）为例，四轮迭代各自暴露的问题：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;轮次&lt;/th&gt;
&lt;th&gt;典型问题&lt;/th&gt;
&lt;th&gt;处理方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;第一轮&lt;/td&gt;
&lt;td&gt;不知道 KMP 侧已有文件读写、日志、埋点能力，自己实现了一遍接口&lt;/td&gt;
&lt;td&gt;把能力文档补进知识库&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;第二轮&lt;/td&gt;
&lt;td&gt;逻辑顺序颠倒、引用判断写错&lt;/td&gt;
&lt;td&gt;按人工审查意见让 AI 修复&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;第三轮&lt;/td&gt;
&lt;td&gt;配置读写没有使用专门的封装类&lt;/td&gt;
&lt;td&gt;引导使用规范组件替代桥接调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;第四轮&lt;/td&gt;
&lt;td&gt;没有使用现成的时间工具组件、用了 &lt;code&gt;!!&lt;/code&gt; 非空断言&lt;/td&gt;
&lt;td&gt;按编码规范修正&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;四轮下来，这个类的绝大多数转写结果被直接采纳——以排除空行和注释后的有效代码行计，被人工替换或删除的只是零星几处。四轮迭代的问题也有清晰的层次：第一轮是知识缺口，第二轮是逻辑错误，第三、四轮已经收敛到编码规范。知识库每补一块，下一个模块就少踩一类坑。&lt;/p&gt;
&lt;h2&gt;工具链的演进&lt;/h2&gt;
&lt;p&gt;转写工具链不是一次设计出来的，实践过程中改了五轮，按时间顺序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把通用命令改成迁移专用命令，补上审查命令。&lt;/li&gt;
&lt;li&gt;引入进度追踪文件，把知识库拉取改成自动执行。&lt;/li&gt;
&lt;li&gt;强制 SPI 学习步骤，支持 &lt;code&gt;.h&lt;/code&gt; 加 &lt;code&gt;.m&lt;/code&gt; 配对输入，精炼翻译指南。&lt;/li&gt;
&lt;li&gt;补环境配置：输出 token 上限的环境变量、本地路径与相对路径引用。&lt;/li&gt;
&lt;li&gt;完善核心规则文档，规范任务描述格式。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中翻译命令最终稳定在 274 行——包含翻译规则和目标代码模板。这个规模是试出来的：太短则约束不住输出风格，太长则重要规则被淹没。&lt;/p&gt;
&lt;h2&gt;边界&lt;/h2&gt;
&lt;p&gt;这次实践对「AI 能转什么」给出了一个可操作的判断标准：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;适合 AI 转写 = 依赖干净 + 不碰 UI&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;适合的：纯业务逻辑、数据处理、模型封装、工具类。不适合的：UI 组件、平台强相关的 API 调用、复杂状态管理。&lt;/p&gt;
&lt;p&gt;优势是明确的。熟悉的 Objective-C 代码转成 Kotlin 后，省掉了逐行查文档的时间；AI 产出的代码会自然用上 Kotlin 的扩展函数、数据类和 lambda，对刚接触 Kotlin 的 iOS 开发者反而是学习材料。&lt;/p&gt;
&lt;p&gt;限制同样明确。UI 层强依赖 UIKit 的框架和生命周期，不解耦就没法转，解耦本身是人的工作；跨文件的前后依赖，AI 的理解仍然需要文档辅助；转写产物必须经过完整测试，这部分成本省不掉。&lt;/p&gt;
&lt;h2&gt;可复用的做法&lt;/h2&gt;
&lt;p&gt;如果要在别的模块或别的团队复现这套流程，四条经验按重要性排序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;纯逻辑优先。让 AI 负责逻辑和数据结构的转换，把平台差异留给人。&lt;/li&gt;
&lt;li&gt;UI 先解耦再谈迁移。没有解耦的 UI 代码不要投给 AI。&lt;/li&gt;
&lt;li&gt;能力文档持续补充。每轮迭代暴露的知识缺口当场补进知识库，收益给到之后的所有模块。&lt;/li&gt;
&lt;li&gt;人工审查不省略。AI 的自查能收敛规范问题，逻辑正确性仍然靠人把关。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;下一步是把这套流程推到更多依赖干净的纯逻辑组件上，再看「代码分析 → 转写 → 审查 → 合入」还有多少环节可以接起来。&lt;/p&gt;
</content:encoded></item><item><title>评测的尺子：设计 AI 评测的几个原则</title><link>https://infingrow.asia/blog/ai-evaluation-ruler/</link><guid isPermaLink="true">https://infingrow.asia/blog/ai-evaluation-ruler/</guid><description>给 Agent 的 Skill 做 with/without 对照评测、给知识库问答建评测集的过程中，我确立了几个原则：统一 Q&amp;A 协议、锁定 judge 版本、只评语义不评形式、结果可重放可审阅、复核结论改回源头。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;做 AI 工具和知识库，每次改动之后都要回答同一个问题：这次到底有没有变好？提示词调了一版，知识库补了一批文档，感觉上变好了——但感觉在这个领域几乎不可信。过去一段时间，我给 Agent 的 Skill 做 with/without 对照评测（Skill 是挂载给 Agent 的领域操作手册，对照评测就是同一批任务在有它和没它两种条件下各跑一遍，比较结果），也给知识库问答建过评测集。这个过程中确立的几个原则，记在这里。&lt;/p&gt;
&lt;h2&gt;统一成 Q&amp;amp;A：问题、标准答案、评判&lt;/h2&gt;
&lt;p&gt;被评测的任务有两类：问答型，考知识库能不能答对一个领域内的问题；编码型，考 Agent 能不能正确修复一个缺陷。一开始很容易想给编码型做强校验：检查 diff 是否命中预期文件、改动位置是否一致。我反对了这条路，把两类评测统一归一化为 Markdown 的 Q&amp;amp;A 协议，每个 case 三件套：问题、标准答案（golden）、评判标准（judge）。三者分工明确：问题交给被测方，golden 只给评判环节看，judge 定义两者怎么比对。评测跑起来就是三步——被测方拿到问题、产出回答，judge 对照 golden 打分并给出分析。&lt;/p&gt;
&lt;p&gt;反对强校验的理由，我当时的原话是「有正确规划，写对是问题不大的」。模型给出正确规划之后，具体写法存在大量合法变体：文件可以拆分，函数可以换名，等价实现也可以有好几种。强校验 diff 或文件位置，会把这些合法变体统统判错，引入大量噪音，同时丢掉真正想考察的东西——模型有没有理解问题、给出正确的解法意图。文档形式反而更完备地描述了问题和解法：一份好的方案文档，比一个恰好匹配的 diff 携带更多信息。&lt;/p&gt;
&lt;h2&gt;输入先清洗，答案要隔离&lt;/h2&gt;
&lt;p&gt;评测输入不能拿原始材料直接用。真实的问题记录是杂乱的，混着无关上下文和当时的讨论过程，直接当被测输入，考察的东西就不纯粹了。所以先把原始材料清洗成规范化的问题描述文件，再进评测。清洗产物按正式文档的标准写：背景、现象、约束条件交代清楚，让被测方拿到的信息量和当时真正处理这个问题的人对齐——多了会泄题，少了又考不出真实水平。&lt;/p&gt;
&lt;p&gt;清洗时同时做答案隔离：输入不得泄漏修复答案。原始材料里经常带着结论，比如问题记录的末尾往往跟着当时的处理方式。隔离不干净，评测测的就是「抄答案」，分数再高也说明不了任何能力。&lt;/p&gt;
&lt;h2&gt;Judge 是尺子，尺子要锁定&lt;/h2&gt;
&lt;p&gt;judge prompt 决定每个 case 怎么打分，它就是评测的尺子。对尺子我只有一条规则：版本锁定，不许原地修改。发现 judge 写得不好，正确的做法是新建 v2、把评测配置切换过去，然后重跑全部历史 case，之后才采信新分数。&lt;/p&gt;
&lt;p&gt;尺子变了，历史分数就全部失效。原地改过 judge 之后，上周的分数和今天的分数出自两把不同的尺子，横向比较失去意义——而评测存在的全部价值就在于比较。锁定版本麻烦，重跑历史 case 也有成本，但这是让分数可信的最低代价。&lt;/p&gt;
&lt;p&gt;最常见的破坏方式很隐蔽：复核时看到一个明显的误判，顺手在 judge 里补一句规则。单看这是修复，实际效果是悄悄换了尺子，这次的分数和所有历史分数从此不可比。所以哪怕只改一句，也要走新建版本加重跑的流程。&lt;/p&gt;
&lt;h2&gt;只评语义，不评形式&lt;/h2&gt;
&lt;p&gt;judge 的评分标准里写死了几条，针对的都是评测里最常见的假阴性——答案是对的，分数是错的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不因代码写法、措辞、排版与标准答案不同而扣分。考察的是语义等价，字面差异不计。&lt;/li&gt;
&lt;li&gt;标准答案支持多解。一个问题存在多种正确解法时，golden 里并列写出，被测答案与其中任何一解语义一致即命中。&lt;/li&gt;
&lt;li&gt;只读评测不因「没改文件」扣分。有些评测场景里，被测 Agent 只允许读仓库、输出分析文档，这时评测是「文档对文档」的协议，judge 拿输出文档和标准答案比对。Agent 没有实际改文件、没有跑命令，都不构成扣分理由，协议本身就没要求这些。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这几条合起来就是：评意图和语义，不评实现路径的字面形态。&lt;/p&gt;
&lt;h2&gt;评测集本身就是知识&lt;/h2&gt;
&lt;p&gt;一个 case 由真实问题加验证过的解答组成，这本身就是知识。所以评测集按知识的标准来维护：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 AI 友好，也对人友好。格式规范到 Agent 能直接解析，人打开也能直接读懂。一份好的评测集读起来应该像一份可靠的 FAQ，翻一遍就了解这个领域常见的问题和正确的处理方式。&lt;/li&gt;
&lt;li&gt;跟随仓库一起迭代。代码或知识变了，相关 case 同步修订，评测集不做只增不改的档案。&lt;/li&gt;
&lt;li&gt;配几个自检 case。答案显然、必须得满分的用例，用来验证评测链路本身没坏。链路坏了跑出来的分数，比没有分数更危险。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;可重放，可审阅&lt;/h2&gt;
&lt;p&gt;被评测对象固定用 commit 来 pin，不用分支。分支会移动，钉在分支上的评测过一个月就无法复现；钉到 commit，任何时候都能重放当时的组合，结论才经得起复查。代码仓库、Skill、知识库内容都适用这条：凡是会随时间变化的被测对象，评测记录里都要留下一个确切的版本坐标。&lt;/p&gt;
&lt;p&gt;结果输出有两条要求。第一，不能只给总分。每个失败的 case 要能定位到：原始问题是什么、参考答案是什么、AI 实际答了什么、judge 的分析和证据在哪里。只有总分的报告没法行动，分数跌了之后，第一个要回答的问题一定是「跌在哪」。&lt;/p&gt;
&lt;p&gt;第二，提供两条消费通道。一条是结构化产物，Agent 可以接着处理，比如批量归因失败的 case；另一条是人可以直接审阅的页面。同一份结果服务两类读者，哪一条断了，评测都会退化成只有跑的人自己看的报表。结果放在稳定目录里，保留模型与配置组合、输入、输出和评判依据，任何一次运行可追溯，任意两次运行可横向比较。&lt;/p&gt;
&lt;h2&gt;复核结论要改回源头&lt;/h2&gt;
&lt;p&gt;评测跑完、人工复核完，结论不能停在一次报表上。复核发现知识错了就改知识，提示词弱了就改提示词，Skill 缺覆盖就补 Skill。评测报告只是中间产物，最终产出是对系统本身的修正，而下一轮评测的分数，就是这次修正的验证。&lt;/p&gt;
&lt;p&gt;评测的全部意义，是让「有没有变好」从感觉变成可以回答的问题。尺子本身不产生进步，但没有尺子，进步和退步都看不见。&lt;/p&gt;
</content:encoded></item><item><title>像管理代码一样管理提示词：QuickGPT 的设计</title><link>https://infingrow.asia/blog/quickgpt-prompts-as-code/</link><guid isPermaLink="true">https://infingrow.asia/blog/quickgpt-prompts-as-code/</guid><description>介绍我的开源 Raycast 扩展 QuickGPT：用 HJSON 文件和 Git 管理 300 多条提示词，用占位符系统把提示词变成随处可调用的工具。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;提示词散落各处，没法迭代&lt;/h2&gt;
&lt;p&gt;我的提示词一度散落在各个地方：备忘录里存一些，聊天记录里留一些，还有一些填在各个 AI 工具的自定义指令框里。用的时候靠翻，翻不到就凭记忆重写一遍。&lt;/p&gt;
&lt;p&gt;数量少的时候这不算问题。等积累到上百条，三个缺陷就藏不住了。第一，没法版本管理：一条提示词前后改过几个版本，旧版本丢在哪里不知道，改坏了也回不去。第二，没法迭代：好的提示词是逐次打磨出来的，第一版能用，第五版才顺手；散落存放意味着每次都从头写，之前的打磨全部作废。第三，没法带上下文：提示词是死文本，每次使用都要手动把选中内容、剪贴板、相关文件一段段贴进去。&lt;/p&gt;
&lt;p&gt;2023 年我开始写 QuickGPT，一个开源的 Raycast 扩展（Raycast 是 macOS 上的快捷启动器，支持第三方扩展），代码在 github.com/ddhjy/quickgpt-raycast。到今天它管理着我个人 300 多条提示词。整个工具围绕两个理念设计：manage prompts like code，把提示词当代码管理；use prompts like tools，把提示词当工具使用。&lt;/p&gt;
&lt;h2&gt;Manage prompts like code&lt;/h2&gt;
&lt;p&gt;提示词以 HJSON 文件存放。HJSON 是对人友好的 JSON 变体，支持注释和多行字符串——写提示词这种多段长文本，不用像 JSON 那样把换行转义成 &lt;code&gt;\n&lt;/code&gt;，还可以在某条提示词旁边加注释说明适用场景。一条提示词大致长这样（字段有省略）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  # 提交代码前自检用
  title: 代码评审
  content:
    &apos;&apos;&apos;
    请评审以下改动，指出问题并给出修改建议：
    {{diff}}
    &apos;&apos;&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些文件放进 Git 仓库，代码怎么管，提示词就怎么管：每次修改有 diff，能看到一条提示词从第一版到现在改过什么；改坏了可以回滚；多台设备之间用 Git 同步，换电脑 clone 下来就是全部。&lt;/p&gt;
&lt;p&gt;版本管理带来的最大变化是：打磨有了去处。用一条提示词时对输出不满意，顺手改措辞、补约束，commit 之后这次不满意就成了记录在案的一次修改，下次在它基础上继续改。散落存放时同样的不满意没有去处，只能等下一次从头再写，然后在同一个地方再不满意一遍。&lt;/p&gt;
&lt;p&gt;这套做法解决了「散落」三个缺陷里的前两个。第三个——没法带上下文——靠第二个理念解决。&lt;/p&gt;
&lt;h2&gt;Use prompts like tools&lt;/h2&gt;
&lt;p&gt;提示词配上动态占位符之后，就从静态文本变成了「随处可调用的工具」：在任何应用里通过 Raycast 全局唤起，占位符在唤起那一刻自动填充成当下的内容。&lt;/p&gt;
&lt;p&gt;占位符系统是 QuickGPT 的技术核心，常用的有这些：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{{input}}&lt;/code&gt;：唤起时手动输入&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{selection}}&lt;/code&gt;：当前选中的文本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{clipboard}}&lt;/code&gt;：剪贴板内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{currentApp}}&lt;/code&gt;：前台应用名&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{browserContent}}&lt;/code&gt;：浏览器当前页面内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{diff}}&lt;/code&gt;：当前仓库的 git diff&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{file:路径}}&lt;/code&gt;：注入指定文件的内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{option:key}}&lt;/code&gt;：交互式下拉选项&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{selection|clipboard}}&lt;/code&gt;：fallback 链，选中文本为空时自动退到剪贴板&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;单看每一个都平常，组合起来才有意思。一条「代码评审」提示词引用 &lt;code&gt;{{diff}}&lt;/code&gt;，唤起时自动带上当前仓库的改动，评审要求和被评审的代码一次就位；一条「翻译」提示词用 &lt;code&gt;{{selection|clipboard}}&lt;/code&gt;，不管是选中了一段文字还是刚复制了一段，一键可用，不用关心内容从哪来。可枚举的参数则交给 &lt;code&gt;{{option:key}}&lt;/code&gt; 做成下拉选项，唤起时选一下，一条提示词顶原来的好几条。&lt;/p&gt;
&lt;p&gt;对比一下两种使用方式。没有占位符：找到提示词，复制，切到 AI 工具，粘贴，再回去复制要处理的内容，再粘贴。有占位符：在任何应用里唤起，内容已经组装完毕。前者的摩擦不大，但一天重复很多次，足以决定一条提示词是被用起来还是被遗忘。&lt;/p&gt;
&lt;p&gt;散落存放还有一个隐含成本：提示词绑定在各自的工具里，浏览器插件里的出不了浏览器，某个客户端里的出不了客户端。占位符加全局唤起把这层绑定拆掉了——提示词属于系统层，在哪个应用里都能用。&lt;/p&gt;
&lt;h2&gt;忽略规则是数据边界&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;{{file:路径}}&lt;/code&gt; 指向目录时，会把目录内容送进模型。目录里未必都是该给模型看的东西：构建产物、体积很大的资源文件、带密钥的配置。&lt;/p&gt;
&lt;p&gt;QuickGPT 读目录时叠加两层忽略规则：&lt;code&gt;.quickgptignore&lt;/code&gt; 和 &lt;code&gt;.gitignore&lt;/code&gt;，语法与 gitignore 相同，用来排除生成物、大文件和私密配置。&lt;/p&gt;
&lt;p&gt;我的立场是：忽略规则是数据边界，不应该为了「上下文更完整」而绕过。工具的方便和数据边界冲突时，边界优先。这条原则写进代码比写在文档里有效——默认就不读，好过依赖使用者每次自觉。&lt;/p&gt;
&lt;h2&gt;一些顺手的设计&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;嵌套 subprompt：提示词可以分层组织，子提示词继承父级属性，同类提示词的公共部分只写一次。&lt;/li&gt;
&lt;li&gt;deeplink：每条提示词有自己的链接，可以携带占位符参数直达。其他工具由此能一键调起某条提示词，不需要先打开 QuickGPT 再搜索。&lt;/li&gt;
&lt;li&gt;拼音搜索：提示词标题多是中文，搜索时敲拼音首字母就能命中。&lt;/li&gt;
&lt;li&gt;临时文件目录 7 天自动过期清理，中间产物不会越积越多。&lt;/li&gt;
&lt;li&gt;与 FinderLink 打通：FinderLink 是我的另一个工具，维护文件路径别名；这些别名可以直接写进 &lt;code&gt;{{file:}}&lt;/code&gt; 里解析。两个工具之间没有代码依赖，只有格式约定——个人工具之间用约定互联，比互相引用轻得多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Raycast 扩展的两个通用坑&lt;/h2&gt;
&lt;p&gt;开发过程中有两个坑值得记录，写任何 Raycast 扩展都可能遇到。&lt;/p&gt;
&lt;p&gt;第一个是 &lt;code&gt;getSelectedText()&lt;/code&gt; 的调用时机。扩展窗口打开之后，焦点已经被 Raycast 抢走，这时再调 &lt;code&gt;getSelectedText()&lt;/code&gt; 经常失败，代码随即错误地回退到剪贴板——用户明明选中了 A，扩展却拿到了剪贴板里的 B，而且并非每次复现，排查起来很费劲。修复模式是在模块加载的最早期就发起捕获并保存 promise，后续需要选中文本时复用这个 promise，不要用时现取。&lt;/p&gt;
&lt;p&gt;第二个是不可见字符。从各种应用里选中或粘贴出来的文本，首尾可能混着零宽字符、U+FFFC（对象替换符）、控制字符。不清理的话，搜索匹配和提示词拼接会出现玄学问题：两段肉眼完全一样的文本，程序里就是不相等。对所有外部来源的文本清理首尾不可见字符，应该当成固定步骤。&lt;/p&gt;
&lt;h2&gt;提示词库是基础层&lt;/h2&gt;
&lt;p&gt;300 多条提示词的价值不在数量。它们连同 Git 历史一起，构成一份「我怎么用 AI」的可迭代记录：哪类任务交给模型、用什么话说、这些话怎么一步步变准。每次 commit 都是一次打磨的痕迹，翻历史能看到自己使用方式的变化。&lt;/p&gt;
&lt;p&gt;提示词库是个人 AI 工作流的基础层。后来我做的东西——聚合上下文的工具、Agent 工作流——都建立在这层之上。基础层稳定，上面的东西才搭得起来。&lt;/p&gt;
&lt;p&gt;QuickGPT 的完整介绍（技术要点、设计决策与当前状态）在它的&lt;a href=&quot;https://infingrow.asia/projects/quickgpt&quot;&gt;项目页&lt;/a&gt;。&lt;/p&gt;
</content:encoded></item><item><title>从人工到自动化：用 LLM 筛选值得复盘的 CR 评论</title><link>https://infingrow.asia/blog/cr-comment-screening/</link><guid isPermaLink="true">https://infingrow.asia/blog/cr-comment-screening/</guid><description>AI 初筛加人工复核：靠对话式的上下文组织和数据集化的批量调优，把 CR 评论复盘的人工筛选工作量减少约三分之二的实践记录。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;好评论淹没在全量评论里&lt;/h2&gt;
&lt;p&gt;团队这几年一直在建设 Code Review 文化，其中一个具体动作是定期复盘：把有价值的 CR 评论整理出来，在团队里过一遍——哪些评论拦下了真实缺陷，哪些指出了设计问题，哪些把一个人的经验变成了大家都能用的知识。复盘做得好，CR 会从「流程要求」慢慢变成工程师真正在意的事。&lt;/p&gt;
&lt;p&gt;麻烦出在筛选这一步。评论总量很大，其中绝大多数是「笔误」「命名再斟酌一下」这类日常沟通，值得拿出来复盘的只占很小的比例。原来的做法是安排人从全量评论里手工筛，问题有两个：一是量大，筛选的人负担很重；二是判断标准不稳定，不同的人筛出来的结果差异明显，同一个人筛到后面，标准也会慢慢漂移。&lt;/p&gt;
&lt;p&gt;2024 年我们和 QA 团队合作改造了这个流程，改成「AI 初筛 + 人工复核」，我负责其中 AI 初筛部分的优化。&lt;/p&gt;
&lt;p&gt;先把命名说清楚。这个项目统一叫「优质 CR 评论筛选」，我们刻意避免叫它「AI Code Review」。后者通常指让模型直接审代码、产出评论；在这个项目里，评论全部由人写，AI 只做一件事：从海量人写的评论中，筛出值得团队复盘学习的那一部分。两者的技术难度和风险完全是两码事。&lt;/p&gt;
&lt;h2&gt;整体思路：AI 初筛，人工复核&lt;/h2&gt;
&lt;p&gt;流程设计很直接：AI 先把明显没有复盘价值的评论过滤掉，再把剩下的候选按价值高低排序；人工只复核头部候选，从中挑出最终进入复盘材料的条目。&lt;/p&gt;
&lt;p&gt;选排序而非只做二分类，是给复核的人留操作空间：按顺序往下看，看到候选质量明显下降就可以停，复核量能按当期的时间预算灵活伸缩。&lt;/p&gt;
&lt;p&gt;这个分工背后有一个判断：筛选类任务对两类错误的容忍度是不对称的。错杀——好评论被排到靠后——有人工复核兜底，只要头部候选能覆盖住大多数好评论就够用；漏杀——个别好评论没进候选集——代价也可控，复盘本来就是选例子讲道理，漏掉几条不致命。两头都有余量，AI 不需要达到人类专家的判断水平，就已经可以开始创造价值。&lt;/p&gt;
&lt;h2&gt;效果提升最大的一项：把上下文组织成对话&lt;/h2&gt;
&lt;p&gt;最早的版本，输入基本就是评论文本本身，效果一般。原因不难理解：单看一句「这里建议再确认一下边界条件」，谁也判断不出它背后是一次真实的缺陷拦截，还是一句客套。&lt;/p&gt;
&lt;p&gt;一条 CR 评论从来都挂在具体的代码行上，前后有作者的回复，有修改前后的代码。这些信息合在一起，才构成判断价值的完整依据。整个优化过程中效果提升最大的一项改动，就是把这些上下文按「对话式」重新组织，让模型看到的输入接近一段有头有尾的叙事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这次修改的代码 diff 是什么，评论挂在哪几行上；&lt;/li&gt;
&lt;li&gt;评论者针对这段代码说了什么；&lt;/li&gt;
&lt;li&gt;作者如何回应，中间经过了几轮讨论；&lt;/li&gt;
&lt;li&gt;代码最终改没改、怎么改的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样的信息量，组织成「谁在什么代码上说了什么、对方如何回应、最后发生了什么」的结构，效果比把评论、代码、回复碎片式地拼在一起好得多。这条经验后来在其他 AI 功能上被反复验证：模型对结构化叙事的理解，远好于碎片拼贴。&lt;/p&gt;
&lt;h2&gt;把「有价值」写成可执行的标准&lt;/h2&gt;
&lt;p&gt;另一块投入是提示词调优。「有价值」三个字，每个人的理解都不一样，直接丢给模型，得到的只会是模型自己的理解。我们把判断标准明确写成三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;拦截了真实缺陷：评论指出的问题如果不修，会变成线上问题；&lt;/li&gt;
&lt;li&gt;指出了设计问题：接口设计、模块边界、可扩展性这类超出单行代码的意见；&lt;/li&gt;
&lt;li&gt;传播了可复用的知识：评论讲清楚了一个别人不知道、以后用得上的机制或约定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每条标准都配了正例和反例。反例的作用是把标准的边界画清楚——什么样的评论看起来认真、实际不值得复盘——比单纯给定义更能约束模型的判断。&lt;/p&gt;
&lt;h2&gt;工程化：把调优变成有数据集支撑的流程&lt;/h2&gt;
&lt;p&gt;调优的过程本身也值得一写。最早的迭代方式是纯手工的：改一版提示词，往对话框里粘几条评论看看效果，感觉不错就再换几条试试。这种方式有两个致命问题：样本太少，结论不可信；改动之间没有可比性，调了几轮之后，说不清到底哪次改动真正有效。&lt;/p&gt;
&lt;p&gt;为了摆脱手工反复粘贴，我们开发了一个调优工具（内部名 TTAIToolbox），把调优变成有数据集支撑的流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据集组织：把带人工标注的评论集固定下来，作为每次迭代的统一基准；&lt;/li&gt;
&lt;li&gt;可视化编排：筛选流程的各个环节编排成可视的流程图，单独调整某一环，不影响其余部分；&lt;/li&gt;
&lt;li&gt;批量执行：一次改动可以在全量数据集上跑完，直接看指标变化；&lt;/li&gt;
&lt;li&gt;组合对比：多个模型、多版提示词组成组合矩阵批量执行，横向对比效果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有了这套工具，「感觉变好了」变成「在同一个数据集上效果变了多少」，迭代速度和结论的可信度都完全不同。&lt;/p&gt;
&lt;h2&gt;结果与复用&lt;/h2&gt;
&lt;p&gt;上线后的分工符合预期：AI 初筛过滤掉大部分明显无价值的评论，人工只复核头部候选集，筛选工作量减少约三分之二。判断标准写进了提示词，筛选结果也不再随筛选者的状态波动。&lt;/p&gt;
&lt;p&gt;「数据集 + 批量执行 + 组合对比」这套做法，后来成了我们调优其他 AI 功能的默认方式。一个直接的例子是 AI 技术评审总结——用模型自动生成技术评审的总结——用同样的方法调优之后，单次耗时降到了原来的约三分之一。&lt;/p&gt;
&lt;h2&gt;筛选类任务是 LLM 的舒适区&lt;/h2&gt;
&lt;p&gt;最后是几点做完之后的体会。&lt;/p&gt;
&lt;p&gt;筛选类任务对 LLM 格外友好，条件有三个：判断标准能用自然语言写清楚；错杀有人工复核兜底；漏杀的代价可控。三个条件同时满足时，模型不需要接近专家水平就能创造实际价值；缺任何一个，方案都要重新评估。拿这三条去检查手头的候选场景，比笼统地问「这个场景能不能用 AI」有用得多。&lt;/p&gt;
&lt;p&gt;对自动化程度的预期同样重要。我们从一开始就只让模型做初筛，把人的工作量从「全量筛选」压到「头部复核」，没有指望它一步到位替代人。这类改造阻力小、见效快：复核的人立刻感到轻松，最终决定权还在人手上，也没有人担心 AI 把关不严。事后看，这个定位上的克制，比任何一项具体的技术优化都更接近这个项目能做成的原因。&lt;/p&gt;
</content:encoded></item><item><title>Code Review 只找真问题</title><link>https://infingrow.asia/blog/code-review-real-issues/</link><guid isPermaLink="true">https://infingrow.asia/blog/code-review-real-issues/</guid><description>AI 参与编码之后，Code Review 的一些老原则反而更重要了。这篇写我的评审方式：先刷新基线再看 diff、只找真实风险、清理 AI slop、核验 AI 意见后再采信，不为凑数量制造意见。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Code Review 做了很多年。AI 参与编码之后，评审对象从纯人写的代码变成人和 AI 混写的代码，量也明显上来了，但怎么审的老原则反而一条条更重要了。这篇写我自己的评审方式，重点有两个：不为凑数量制造意见，核验 AI 意见之后再采信。&lt;/p&gt;
&lt;h2&gt;先刷新基线&lt;/h2&gt;
&lt;p&gt;审查从对齐基线开始。先 fetch 远端的目标分支，再审三点 diff（目标分支以 main 为例）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
git diff origin/main...HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三点 diff 给出的是当前分支相对共同祖先的改动。配合刚刷新的远端引用，看到的才是这次评审真正要审的内容；拿陈旧的本地引用直接比对，diff 里会混进目标分支上早已合并的改动，也可能漏掉它最近的变化。基线错了，后面的工作全部白做。所以我把这一步固定成评审的机械动作：不管对仓库多熟，先刷新、再取三点 diff，然后才开始读代码。&lt;/p&gt;
&lt;p&gt;这一步在 AI 参与评审时尤其重要。人对着错误的 diff 还可能凭记忆起疑，「这段好像上周就合并了」；Agent 没有这层记忆，会对着错误的范围一路审完。&lt;/p&gt;
&lt;h2&gt;界定范围&lt;/h2&gt;
&lt;p&gt;基线之后是范围。我会同时看三样东西：已提交的 diff、工作区里相关的未提交改动、仓库自身的规则和约定。规则和约定指贡献规范、编码约定这类仓库内的既有共识——评审意见与仓库自己的规则冲突时，作者会无所适从，所以先读规则再下判断。三样都看过，明确哪些改动属于本次任务的范围，再开始逐行看。&lt;/p&gt;
&lt;p&gt;范围界定同时防两个方向的错误。一个方向是漏：配套的未提交改动里可能藏着问题，只看已提交部分会错过，而它们迟早会跟着下一次提交进仓库。另一个方向是越界：超出本次任务的历史问题可以提，但要标注为范围外，不和本次改动的问题混在一起算账。&lt;/p&gt;
&lt;h2&gt;只找真问题&lt;/h2&gt;
&lt;p&gt;我的优先级是固定的：真实的正确性、安全、并发、数据、兼容问题排在前面，这些会造成实际损失；风格和品味类的意见排在最后，多数时候干脆不提。判断一条意见值不值得提，我用的标准是：如果作者不改，会发生什么坏事？答不上来的意见先放下。&lt;/p&gt;
&lt;p&gt;不为凑数量制造意见，是我给自己、也给 AI 评审定的规则。一条无关痛痒的风格意见，成本远不止写它的那几秒：作者要阅读、要判断、要回应、要决定改还是不改，几轮往返的时间足够修一个真问题。意见数量从来不代表评审质量，零条意见配一句「未发现问题」是完全合法的产出。&lt;/p&gt;
&lt;h2&gt;清理 AI slop&lt;/h2&gt;
&lt;p&gt;AI 参与编码之后，评审要多看一类问题，我把它们统称为 AI slop。slop 这个词近两年常用来指 AI 批量生成的低质量内容，放到代码里，指的是那些单看无害、累积起来让代码库不可信的东西。常见的有四种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;与上下文不一致的解释性注释。注释说的和代码做的对不上，或者在解释一个一眼就能看懂的赋值。&lt;/li&gt;
&lt;li&gt;异常防御式的 try-catch。为不可能发生的场景兜底，顺手把真正的错误也吞进空的 catch 里。&lt;/li&gt;
&lt;li&gt;只为绕过类型系统的 any 强转。类型对不上，不去修类型定义，用 any 把编译错误压掉。&lt;/li&gt;
&lt;li&gt;明显偏离邻近代码的风格。周围是统一的写法，新代码突然换一种范式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这类问题的麻烦在于单条都「无害」，在评审里很难理直气壮地要求修改。但它们累积起来消耗的是信任：注释不敢信、错误处理不敢信、类型标注不敢信，最后每次读代码都要重新验证一遍。我的做法是把它们明确列为评审目标，发现即提，不因为「小」而放过。提的时候点明类别就够，作者对照类别自查，比逐行争论快得多。&lt;/p&gt;
&lt;h2&gt;核验 AI 的意见，再采信&lt;/h2&gt;
&lt;p&gt;AI 评审的产出我从不直接转发，因为它可能把事实搞错。一个真实案例：AI 评审分析一段 Android 权限代码时，把 API 33 引入的权限组行为报成了 API 34 引入。这条意见推理完整、表述自信，如果直接采信，修复方案里的版本条件判断就会错一个版本。&lt;/p&gt;
&lt;p&gt;从那以后我固定一条规则：凡是涉及 API level、平台约束、配置语义的意见，先查官方文档，再定结论。核验本身不费事：把意见里的关键断言当关键词去查文档，几分钟就有答案。这类事实性断言正是模型容易出错的地方，也因为它表述得具体、肯定，人最容易放松核验。查证不了、又拿不准的意见，就标注为存疑交给作者判断，好过给出一个错误的肯定结论。AI 评审的价值在覆盖面广、成本低，事实裁决要以官方文档为准。&lt;/p&gt;
&lt;h2&gt;影响面分析怎么交付&lt;/h2&gt;
&lt;p&gt;在多端共用的仓库里改公共配置，评审时常要回答「其他端受不受影响」。我要求影响面分析按端逐个写清三件事：是否受影响、理由、当前默认值，最后合成一张表。逐端展开是为了堵住「应该都没问题」这类含糊结论，每个端都必须给出明确判断和依据；记录当前默认值，是因为「不受影响」的结论常常建立在对默认值的假设上，把假设写出来才能被检查；表格让作者一眼看到全貌。&lt;/p&gt;
&lt;p&gt;分析结论还要写成代码内的 TODO 注释，放到对应位置。留在评论区的意见容易被后续讨论淹没，写进代码就成了作者能按 diff 逐个处理、逐个消除的事项。&lt;/p&gt;
&lt;h2&gt;收尾&lt;/h2&gt;
&lt;p&gt;评审的最后一步是运行与风险相称的检查：小改动跑相关测试和静态检查，高风险改动值得完整构建。报告把三类内容分开写：发现的问题、已经修复的、未覆盖的部分，让读的人清楚哪些结论有验证支撑。未覆盖的部分尤其要写出来，它提醒作者和后续评审者哪里还欠一次验证。最后的总结控制在一到三句，评审的价值在具体意见里，收尾写长了没有人看。&lt;/p&gt;
&lt;p&gt;评审输出是给作者消费的。AI 让产出意见变得便宜，一次评审生成几十条意见毫不费力，稀缺的资源变成了作者的注意力。只提真问题，是对这份注意力最基本的尊重。&lt;/p&gt;
</content:encoded></item><item><title>规则、参考、工作流：团队知识该放在哪</title><link>https://infingrow.asia/blog/knowledge-placement/</link><guid isPermaLink="true">https://infingrow.asia/blog/knowledge-placement/</guid><description>Agent 时代，团队知识怎么组织才能被 AI 和人同时用好？我的答案是一个三分法：规则进 AGENTS.md，参考资料进 Wiki，标准工作流写成 Skill。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2026 年我在推动今日头条客户端团队的 AI 知识库规范，要回答的问题只有一个：Agent 时代，团队知识怎么组织，才能被 AI 和人同时用好。&lt;/p&gt;
&lt;p&gt;问题的起点很具体。团队知识散在各处：文档平台上的设计文档、群聊里的排障记录、老工程师脑子里的约定。人找起来已经费劲——新人入职时问的第一批问题，答案大多散落在没人能指路的地方；Agent 则完全用不上，它进入一个仓库时，除了代码什么都看不到。反过来，把所有文档一股脑塞给 Agent 也不行：上下文有限，绝大多数知识和当前任务无关。&lt;/p&gt;
&lt;p&gt;反复整理后，我收敛出一个三分法。&lt;/p&gt;
&lt;h2&gt;三分法&lt;/h2&gt;
&lt;p&gt;按知识的性质分三类，各有各的去处：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;知识类型&lt;/th&gt;
&lt;th&gt;放置位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;必须无条件遵守的项目规则&lt;/td&gt;
&lt;td&gt;AGENTS.md（Agent 每次会话自动加载）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景、架构、领域知识、参考资料&lt;/td&gt;
&lt;td&gt;Wiki（按需检索）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可重复执行的标准工作流&lt;/td&gt;
&lt;td&gt;Skill（按需加载执行）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两个名词给一句上下文：AGENTS.md 是多数 Coding Agent 约定的项目说明文件，会话开始时自动读入；Skill 是 Claude Code、Codex、Cursor 等 Agent 支持的机制，把工作流写成文档，按需加载执行。&lt;/p&gt;
&lt;p&gt;按载入时机看，三类其实是两种。规则型知识强制引入，每次都要在场：代码规范、提交约定、禁止事项，Agent 不知道就一定会犯。参考型知识按需引入，用到再查：某个模块的架构设计，只在改那个模块的时候才有用。&lt;/p&gt;
&lt;p&gt;由此推出第一条纪律：AGENTS.md 必须保持短小。它是唯一无条件占用每次会话上下文的文件，把大段参考资料复制进去，等于让所有任务都为它付出上下文成本。参考资料放 Wiki，AGENTS.md 里只放规则和入口。&lt;/p&gt;
&lt;p&gt;Skill 按触发方式再分两种：AI 能可靠判断触发条件的工作流，做成自动触发；涉及敏感操作、或需要用户做选择的，做成手动触发。&lt;/p&gt;
&lt;p&gt;这样，一个项目的知识加载就分成三层：AGENTS.md 的自动规则，按需检索的 Wiki，按需加载的 Skill 工作流。一次典型的任务流转是：Agent 带着 AGENTS.md 里的规则开工，改到不熟悉的模块时检索 Wiki 补背景，碰到发版这类标准操作时加载对应的 Skill 照流程执行。三层各管一段，互不越界。&lt;/p&gt;
&lt;h2&gt;Wiki 用 Diátaxis 组织&lt;/h2&gt;
&lt;p&gt;Wiki 内部采用 Diátaxis 框架，分教程、操作指南、解释、参考四类：教程带新人完整走一遍，操作指南解决一个具体任务，解释讲背景和设计决策，参考是精确的事实清单。选它就为一件事：让「怎么做」和「为什么」不混在同一篇文档里。混写的文档人读着累，Agent 检索时也拿不准该引用哪一段——查操作步骤的任务用不上历史背景，查设计决策的任务用不上命令清单。&lt;/p&gt;
&lt;h2&gt;Skill 控制在 20 个以内&lt;/h2&gt;
&lt;p&gt;团队 Skill 的数量设了上限：20 个。理由是认知负荷。Skill 列表本身会进入 Agent 的上下文，也会进入维护者的头脑；超过一定数量之后，「找到对的那个」比「新写一个」更贵，接下来就会出现两个高度重复的 Skill，连维护者自己都说不清差别。数量顶到上限时，先合并、删除，再考虑新增。20 这个数字本身谈不上精确，重要的是存在上限：它逼着每次新增之前先回答一个问题——这件事能不能并进已有的 Skill。&lt;/p&gt;
&lt;h2&gt;先读文档，再看代码&lt;/h2&gt;
&lt;p&gt;工作流层面有一条关键约定：Agent 进入任务时先查知识库，再翻代码。这条不立，Agent 的默认行为是直接读代码推断一切——推断得往往还不错，于是团队知识永远没有被引用的机会，写了等于白写。知识库的价值和这条工作流约定绑在一起，要推就一起推。&lt;/p&gt;
&lt;h2&gt;一处权威，别处引用&lt;/h2&gt;
&lt;p&gt;更新知识之前先判断：这件事是否已经有唯一权威来源？有，就链接过去，不复制正文。复制出来的第二套会漂移，过几个月两边说法不一致，读者不知道该信谁。全局知识库尽量只保存入口和稳定摘要，正文留在它的权威位置。判断的成本很低，检索一次而已；不判断的代价是库里渐渐出现几篇标题相似、内容各自演化的文档，后来的维护者只能靠猜。&lt;/p&gt;
&lt;h2&gt;检索靠默认能力&lt;/h2&gt;
&lt;p&gt;知识库的检索，默认依赖 Agent 原生的文件检索能力（比如 &lt;code&gt;rg&lt;/code&gt;），加上清楚的内容结构：目录名达意、标题准确、正文里出现该出现的关键词，模型就找得到。没有真实的能力缺口时，不额外维护一套搜索脚本或索引服务——我在个人 Skill 库上为这个结论付过一次学费。&lt;/p&gt;
&lt;p&gt;同样的判断放大一层，就是「要不要上 RAG」。我的立场是 agentic search 优于 RAG 同步文档库：把文档拉到本地用 Git 管理，让 AI 直接检索文件，也让 AI 参与优化知识结构。文档进了 Git，知识的每次变更就有了和代码一样的历史与评审。一句话概括：RAG 解决怎么找资料，知识编译解决怎么炼知识。向量检索能把相关段落捞出来，但改不动文档本身；本地化的知识库，AI 发现两篇文档冲突可以直接改，发现结构不合理可以重组。知识库要可被 AI 优化、可迭代，而不只是可检索。&lt;/p&gt;
&lt;p&gt;顺带一句：评测集也是知识。一个问题加上它验证过的解答，本身就是团队知识的一部分，值得和文档放在一起管理。评测怎么建，单独写在&lt;a href=&quot;https://infingrow.asia/blog/ai-evaluation-ruler&quot;&gt;《评测的尺子》&lt;/a&gt;里。&lt;/p&gt;
&lt;h2&gt;配套机制&lt;/h2&gt;
&lt;p&gt;三分法要运转起来，还差两个配套机制。&lt;/p&gt;
&lt;p&gt;一是 wiki-lookup 和 wiki-update 这对工作流。前者让 Agent 按需查找和引用团队知识，后者让它把任务中新确认的知识写回知识库。写回的时机很自然：一次排障收尾、一个方案定稿，Agent 手里正握着刚验证过的结论，这时候顺手更新知识库，成本最低。只查不写，知识库会停在建库那天的状态。&lt;/p&gt;
&lt;p&gt;二是 Skill 的统一分发。团队成员用的 Agent 各不相同，Claude Code、Codex、Cursor 都有，各家读取 Skill 的目录还不一样。分发工具的做法是维护单一的 Skill 源仓库，用软链接把它安装进各个 Agent 的目录——写一份，处处可用，改动只发生在一处。&lt;/p&gt;
&lt;p&gt;这套规范目前的状态：两类知识的职责边界和 Skill 统一架构已经定稿，组织核心业务方评审过一轮，正在试点接入。推进到现在，我最大的体会是：三分法本身不难想，难守的是配套纪律——AGENTS.md 保持短小、Skill 数量设上限、先读文档再看代码。结构一天就能定下来，纪律要靠一次次评审守住。等试点跑出足够的使用数据，才能回答下一个问题：知识库被 Agent 引用的频率，到底有没有高到值得这套投入。&lt;/p&gt;
</content:encoded></item></channel></rss>