我的 AI 工作流写了两年,直到 Opus 4.8 才真正生效
我的 CLAUDE.md 里一直有这么几条:
- 以证据为准,不靠猜测:API 行为、库能力、文件路径、配置语义都要查实
- 未证实前不说「可以/已支持」;区分「已改」与「已验证」------未跑过的代码
- 不确定时用
// @TODO占位或提问,禁止编造函数/API
以前的模型看完这几行,该编还是编。你问它「这个 API 支持吗」,它照样一本正经地说支持,然后给你一个不存在的方法名。
Opus 4.8 之后不一样了。 它会主动怀疑自己,反复去验证:
- 他会主动说「我没验证过,你跑一下」,而不是「已实现」
- 真的去用
gh api拉 Github 源码确认,而不是凭记忆答 - 不确定的地方留
@TODO,而不是编一个像模像样的函数名
规则一个字没改,是模型终于愿意遵守了。
而「不许猜」这条规则一旦真的生效,第一个要回答的问题就变成了:不猜的话,那要去哪里查。
所以这篇从查资料开始讲。全文所有配置都在 beixiyo/dotfiles 里,文末有 skill 清单,复制就能用。
下面的配置格式是用 ClaudeCode 的,如果你用其他的工具,可用我写的 ai-sync 一键同步过去,比如 Codex、OpenCode、Gemini...
一、先把「去哪里查」定死
AI 遇到不熟的库最爱编 API。治它的办法不是在规则里多写两句「请查证」------而是把搜索顺序写死,不许它上来就调用内置的 Web-Fetch。
这就是 /search 这个 skill 干的全部事情。它自己不产出调研内容,只做一件事:路由。
text
1. 查文档 context7-mcp ← 第一优先级,已知是某个库/框架/CLI 的用法
2. 查仓库 github skill(gh) ← 已知 owner/repo,要看源码、文件、Issue、PR
3. 搜代码 gh-grep-mcp ← 不知道该看哪个仓库,只想看大家实际怎么写
4. 联网搜 search-mcp ← 教程、报错信息、事实性问题
5. 兜底 Web Search ← 前面全挂了,或者要强实时的新闻
五个工具各管一段,边界很清楚:
| 工具 | 干什么 | 什么时候用 |
|---|---|---|
| Context7 | 库 / 框架 / SDK / CLI 的最新文档与示例 | 已知具体库、API、配置项、版本差异 |
| gh CLI | 查 GitHub 仓库、文件、分支、Issue、PR | 已知仓库名,要看源码或翻 Issue |
| gh-grep-mcp | 在全 GitHub 按代码模式(literal / 正则)搜 | 不知道该看哪个仓库,想找真实项目的写法 |
| Exa | 给 AI 用的搜索引擎,返回清洗过的纯文本 | 教程、报错信息、事实性问题 |
| Web Search | 内建通用搜索 | 上面全挂了,或者要今天刚出的新闻 |
gh 和 gh-grep-mcp 的分工是重点:知道去哪儿找用 gh,不知道去哪儿找用 gh-grep。
比如「想看看大家实际怎么写 useEffect 清理事件监听」,就该走 gh-grep 的正则搜索:
text
(?s)useEffect\(\(\) => {.*removeEventListener 语言筛 TSX / JSX
为什么不用自带的 Web Search?
三个原因:
- 结果里全是 SEO 垃圾和搬运站,准确的 API 定义埋在底下;
- 网页里一堆导航栏和脚本标签是纯噪音,烧 token 还干扰判断;
- 通用搜索也分不清你要官方文档还是要代码示例。
光有 skill 还不够------skill 得先被想起来才会被调用。所以我在全局 CLAUDE.md 的开发流程里也钉了一遍:
markdown
5. **调研不确定项**:不完全确定的库用法先调用 `search` skill;
GitHub 项目先调用 `github` skill;大型项目需要深度理解时克隆源码
一条给 skill 的 description(让它能被自动触发),一条写死在常驻规则里(让它想不起来也得想起来)。两头都堵上。
gh 就是最好的 GitHub 搜索器
先把 gh 装上登进去,不然都是空谈:
bash
# Mac
brew install gh
# Arch
sudo pacman -S --needed --noconfirm github-cli
# Windows
winget install --id GitHub.cli
# 登录
gh auth login # 交互式,选 GitHub.com → HTTPS → 浏览器授权
gh auth status # 验证;token 过期用 gh auth refresh
Debian / Ubuntu 那串 keyring + apt source 的命令太长,直接抄官方安装说明。
登录这一步别跳过。 不登录虽然也能读公开仓库,但 GitHub REST 对未认证的请求限流
/github 这个 skill 通篇只有几条命令,核心是三行:
bash
# 读文件(GitHub API 返回 base64)
gh api "repos/{owner}/{repo}/contents/{path}" --jq '.content' | base64 -d
# 读 README
gh api "repos/{owner}/{repo}/readme" --jq '.content' | base64 -d
# 列目录,只要名字
gh api "repos/{owner}/{repo}/contents/{path}" --jq '.[].name'
再加上这几条,一个仓库基本就摸透了:
bash
# 先看概况,别直接拉 README 把上下文撑爆
gh repo view {owner}/{repo} --json name,description,primaryLanguage,stargazerCount,url
# 最近 3 条提交,只要 sha / message / author
gh api "repos/{owner}/{repo}/commits?per_page=3" \
--jq '.[] | {sha: .sha[0:7], message: .commit.message, author: .commit.author.name}'
# 翻 Issue 找同款问题
gh issue list --repo {owner}/{repo} --search "{keyword}" --limit 5
--jq 那段是重点。GitHub API 的原始 JSON 大得离谱,不过滤直接把上下文撑爆。 一个 contents 接口返回里,你要的 content 字段之外还挂着一堆 _links、git_url、download_url。
Windows 用户注意,应该没有 jq 工具,Unix-Like 系统就自己装一下
顺带记住 gh api <endpoint> --jq '.field' 这个万能句式------GitHub 所有 REST 端点都能这么调,languages、releases、tags、pulls 换个词就行,不用为每类信息记一条专用命令。
skill 里还有两条约束:包含 ? 或 {} 的 URL 必须加引号(不然 shell 先给你报错);禁止执行任何修改类命令 ------gh repo edit、gh pr merge 这些一律不准碰,这个 skill 只有只读权限。
CLI 比装 GitHub MCP 省太多 token 了。MCP 一启动就要把所有工具的描述、入参、出参全加载进上下文,而 gh 就是一条命令。
我装了哪些 MCP
我的 MCP 一共就这么几个:
jsonc
{
"mcp": {
// 搜索三件套
"context7-mcp": { "type": "local", "command": ["npx", "-y", "@upstash/context7-mcp"] },
"gh-grep-mcp": { "type": "remote", "url": "https://mcp.grep.app" },
"search-mcp": { "type": "remote", "url": "https://mcp.exa.ai" },
// CLI 真的做不到的事
"lsp-mcp": { "type": "local", "command": ["vv-mcp"] },
"db-mcp": { "type": "local", "command": ["npx", "@bytebase/dbhub@latest", "--transport", "stdio", "..."] },
"figma-mcp": { "type": "remote", "url": "https://mcp.figma.com/mcp" },
},
}
注意这个列表里没有 GitHub MCP,没有文件系统 MCP。 不是漏了,是这些事 CLI 全能干,而且干得更省。
上面是 OpenCode 的写法,Claude Code 换成命令行加:
bash
claude mcp add --scope user lsp-mcp -- vv-mcp # 这个需要下载我的插件,nvim/VSCode 专属 https://github.com/beixiyo/vv-mcp.nvim
claude mcp add --scope user context7-mcp -- npx -y @upstash/context7-mcp
二、让 AI 自己去读开源代码
这是我觉得最被低估的一件事。
大部分人还停留在「让 AI 猜」或者「我自己去翻文档粘给它」。其实可以直接让它去把源码拉下来读。
上一节那几条 gh api 适合读一两个文件。要读 5 个以上、或者需要跨目录交叉看的时候,一条条走 gh api 就很蠢了。直接拉源码:
bash
git clone --depth=1 --single-branch --no-tags \
https://github.com/<owner>/<repo>.git /tmp/fsb-<repo>
三个参数作用:
| 参数 | 干什么 | 为什么要 |
|---|---|---|
--depth=1 |
只拉最新一次提交的快照,历史截断 | 你要的是「现在的代码长什么样」,不是这个文件三年前怎么改的。十年老仓库能差出一个量级的下载量 |
--single-branch |
只拉默认分支 | --depth 本来就隐含这条(除非显式 --no-single-branch),写出来是防止哪天去掉 depth 时行为漂移 |
--no-tags |
一个 tag 都不拉 | monorepo 每个包独立发版,tag 能有几千个,全是你不看的引用 |
我把选择标准写进了 /feasibility skill:
| 场景 | 推荐方式 |
|---|---|
| 读 1~2 个文件 / 知道确切路径 | `gh api ... |
| 看某个库的文档 / API | search 路由到 Context7(权威、带版本) |
| 读 5+ 个文件 / 跨目录交叉看 / 要搜仓库内代码 | 浅克隆到 /tmp 本地读 |
| 概念性问题 / 找对比方案 | search(Exa 检索) |
目录命名统一 /tmp/fsb-<repo>,用完问一句要不要清理。
写这篇文章时就用上了 ------我让它去读我自己 ai-sync 仓库的转换器源码,它顺手在里面发现了一个真 bug:Markdown 转 TOML 时把所有反引号行内代码都转成了 Gemini 的 shell 执行语法。我自己写的代码,自己没发现。
而且它没有停在「我觉得这里有问题」。它写了个最小样例、跑了一遍真实的转换函数,把实际输出贴给我:
text
输入:创建名为 `$1` 的组件
输出:创建名为 !{{{arg1}}} 的组件
跑出来的和读出来的,说服力差一个量级。
要读网页、验 UI:/playwright-cli
浏览器这块我之前用的是 Vercel 的 agent-browser,现在换成 playwright-cli 了。
换的原因很简单:Vercel 出品的东西 bug 太多。
顺便说一句,Next.js 也是他们的作品,我是真不喜欢:
- 强绑定框架 ------ 一旦用了就很难迁出去
- 单文件混前后端 ------ 到处
'use client',代码边界糊成一团,维护起来痛苦 - Bug 也很多,出现了很多次严重 Bug,比如 ------ CVE-2025-29927 是个 CVSS 9.1 critical 的鉴权绕过:只要在 middleware 里做权限校验,攻击者带一个
x-middleware-subrequest头就能整个绕过。11.1.4 到 15.2.2 全中招。
回到 playwright-cli。关键配置只有一条------open 必须带 --persistent:
bash
playwright-cli open "https://example.com" --persistent
它会把 localStorage、cookie、会话落盘到默认 profile 目录,下次自动恢复。新站点手动登录一次,之后所有对话都是已登录状态 ,不用每次折腾 state-save / state-load。
工作流是 snapshot 拿元素 ref,然后按 ref 操作:
bash
playwright-cli snapshot # 拿到 e3、e5 这样的 ref
playwright-cli click e3
playwright-cli fill e5 "user@example.com" --submit
playwright-cli eval "document.title"
对 AI 友好在于确定性:ref 是快照给的,不用它自己去猜 CSS 选择器,也不用把整个 DOM 塞进上下文。
三、最省精力的两个 Skill
我配了二十多个 skill,除了上面那两个搜索路由,日常真正高频、且省下大量精力的还有两个。
/feasibility:动手前先论证
最有价值的一个。
以前的流程是:我提个改造需求 → AI 直接开写 → 写到一半发现架构上根本做不到 → 白干,还污染了代码。
现在复杂改造进来先走 feasibility,它必须先产出一份报告才准动手:
text
现状 → 问题/目标 → Github 开源参考 → 候选方案对比 → 推荐 + 改动量 → 可行性结论
几条硬规矩:
markdown
1. **不动手先对齐** --- 可行性结论 + 方案选择必须等用户明确确认,
未经确认禁止 Write/Edit 业务代码
2. **不凭记忆** --- 涉及第三方库 / 开源实现 / 陌生 API 时,
**必须**调用 `search` / `github` skill 查证,不得编造
3. **证据为准** --- 关键结论附可验证来源(仓库 URL、文件路径:行号、文档段落)
4. **量化影响** --- 改动量、风险、API 兼容性要写具体范围,
不用"很小 / 较大"这类模糊词
反例清单里我明确禁掉了这几种:
text
❌ 直接给代码实现 ------ 用户没让你动手,可行性阶段写代码是越权
❌ 「应该可以」「大概没问题」 ------ 结论必须明确 ✅/⚠️/❌
❌ 跳过开源调研 ------ 除非用户明说「凭经验快速估一下」
❌ 自动进入实现 ------ 即使结论是 ✅,也要等用户回复「开工」再动
最后那条尤其重要。不加的话它论证完了顺手就开始改代码,前面的对齐全白费。
/how-to-test:改完直接给我一条能跑的命令
改完代码最烦的一段其实不是写测试,是为了验一下得先把环境搭起来:造几个假文件、准备一份配置、想清楚该拿什么输入去打、再对着输出猜自己期望的是什么。
它把这一整段包了。改完之后我收到的是这样一份东西:
比如刚修完一个分页越界的 bug,它给我的是这个:
markdown
## 测试:分页参数越界修复
### 自动验证
```bash
bun run /tmp/paginate-test/run.ts
```
预期:`4 PASS / 0 FAIL`
- `paginate(list100, { page: 1, size: 10 })` → 第 1~10 条 正常路径
- `paginate(list100, { page: 0, size: 10 })` → 归一化成第 1 页 ← 这次修的
- `paginate(list100, { page: 999 })` → 返回空数组,不抛错 边界
- `paginate([], { page: 1 })` → 返回空数组 空输入
### 手动验证
1. 打开列表页 → 跳到最后一页 → 再点「下一页」
预期:按钮置灰,不出现空白页
复制那条命令,回车,看数字。 不用自己搭 fixture,不用自己想该测什么,也不用自己判断输出对不对------预期是什么它已经写在那儿了。
几条约定让这个「复制粘贴就行」成立:
- 脚本自己清理并重建 fixture,所以可以重复跑,不用管上次留下的垃圾
- 打印
PASS:/FAIL:,最后汇总N PASS / M FAIL,一眼看数字,不用读日志 - 它得自己先跑一遍再告诉我结果------不然给我的就是「理论上应该通过」
- 用真实源码、真实公开 API、真实运行入口,不 mock 掉被测的东西
- 跑完清理临时产物,想留下来看就
KEEP=1 - 给出我能手动测试的真实步骤和预期结果
入口按本机有什么命令自动挑,不用我说用什么跑:
| 条件 | 入口 | 运行 |
|---|---|---|
command -v bun |
run.ts |
bun run /tmp/<slug>-test/run.ts |
command -v node |
run.js |
node /tmp/<slug>-test/run.js |
command -v python3 |
run.py |
python3 /tmp/<slug>-test/run.py |
| 兜底 | run.sh |
bash /tmp/<slug>-test/run.sh |
另一半规则是管住它别写没用的测试,不然「一条命令跑完」就变成了「一条命令跑一堆假绿灯」:
- 先判断测试是否有信号;没有就不写脚本
- 不为源码字符串、className、图标名、文案、import 存在性写断言
- 已被 typecheck/lint 覆盖的语法级问题,不再包一层脚本
- UI 视觉微调、布局观感、浮层焦点、鼠标交互优先手测或截图验证
- 如果只能做低价值自动化,直接说明未新增脚本及原因
最后一条是关键:允许它说「这个不值得测」。 不给这个出口,它就会硬憋一个假测试交差------断言某个 className 存在、断言某个 import 没被删,跑一万次都是绿的,一个 bug 也抓不到。
为什么 /invoke-plan 降权了
大半年前我最看重的是它------复杂需求先生成 plan/xxx.md,把大任务拆成 checklist,每完成一步回来更新状态。
现在这个 skill 的触发条件被我改窄了:
markdown
description: 仅当用户明确要求计划、任务拆解、长期进度记录、分阶段验收,
或任务需要跨多轮维护 progress 文件时使用
原来是「遇到复杂需求就用」。
但是现在模型自己的规划能力上来了。 以前不写 plan 文件,它写着写着就忘了前面改过什么,或者 A 文件的改动把 B 文件的逻辑覆盖了。现在多文件改动它自己拿得住,硬塞一个 plan 文件反而多一层开销。
现在它还有用的场景只剩两个:跨多轮、需要关机之后第二天接上 ,以及要分阶段验收。
四、让 AI 用 LSP 理解本地代码,而不是 grep 查文本
前面两节是读别人的 代码,这节是读自己的代码。
AI 找符号靠什么?grep。
问题是 grep 只认字符串。同名的局部变量、注释里提到的、字符串字面量里的,它全当成命中。反过来,重命名之后的引用、跨文件的类型继承、谁调用了谁,它又找不全。
但你的编辑器里明明跑着 LSP,它什么都知道。
先说清 LSP 是什么
LSP = Language Server Protocol,微软为 VSCode 定的一套协议。它干的事是把「语言智能」从编辑器里拆出去,变成一个独立进程:
text
编辑器(VSCode / Neovim / Emacs...) ←── JSON-RPC ──→ language server
只管显示:高亮、弹窗、跳转 只管回答:
这个符号定义在哪?
它的类型是什么?
谁引用了它?
这行有什么错误?
拆开的好处是 N × M 变成 N + M:tsserver、rust-analyzer、gopls、pyright 写一遍,所有编辑器都能用;编辑器也不用为每种语言各写一套分析器。
所以我写了 vv-mcp.nvim(Neovim)和 vsc-lsp-mcp(VSCode),把编辑器里已经在跑的 LSP 通过 MCP 暴露给 AI。我现在主力是 Neovim,两边功能基本一个意思。
VSCode 那个插件的开发过程我单独写过一篇:《手写 LSP MCP 消除大模型幻觉,让 AI-IDE 真正理解代码》。
「复用编辑器里的 LSP」不是偷懒,而是省内存,省配置。 市面上也有一些独立跑一个 language server 的 MCP,功能看着差不多。
但那意味着同一个项目同时开两份 LSP 服务器,比如 tsserver:你的编辑器一份,AI 一份。大项目里 LSP 是吃内存的大头,直接翻倍。做成 VSCode 插件 / Neovim 插件,等于白嫖已经付过的那份开销------新增内存接近于零。
这也是上面那个 MCP 清单里 lsp-mcp 能占坑的理由:它要跟一个活着的编辑器进程说话
LSP 能回答的是这类 grep 答不了的问题:
- 这个符号解析到哪个声明?
- 哪些是真正的引用,哪些只是文本恰好匹配?
- 谁调用了这个函数,它又调用了谁?(调用层级)
- 当前的类型、签名、诊断、可用的 Code Action 是什么?
- buffer 里还没存盘的内容是什么?
最后一条容易被忽略但很实用------AI 看到的是我编辑器里当前的样子,不是磁盘上那份。
我的全局 CLAUDE.md 里,它跟前面那条搜索规则是并排的两行:
markdown
4. **探索真实代码**:优先使用 LSP MCP 定位符号、定义、引用、调用关系、
类型和诊断;配置、文本或 LSP 无结果时再用文件搜索
5. **调研不确定项**:不完全确定的库用法先调用 `search` skill
先 LSP,查不到再 grep------顺序反过来就等于白装。
装起来是一行(需要下载我的插件,nvim/VSCode 专属 github.com/beixiyo/vv-...
bash
claude mcp add --scope user lsp-mcp -- vv-mcp # Claude Code
codex mcp add lsp-mcp -- vv-mcp # Codex
用法上有个反直觉的点:先查符号,再查位置
MCP 的 instructions 里我写死了一条调用纪律:
传原生绝对路径和 1-based 位置。符号位置不确定时,先用
document_symbols(已知文件)或workspace_symbols(全项目)定位,再复用返回的 range start。写操作一律走 preview → apply。
为什么要多绕一步?因为让 AI 自己数行号是不可靠的。它读到的文件可能被截断过、可能是几轮对话之前的版本、可能它就是记错了。你让它「查一下第 42 行第 17 列那个符号的类型」,它给的 42 和 17 是猜的,查出来的 hover 是隔壁变量的。
而 document_symbols 返回的 range 是 language server 自己算的,直接拿来当下一次请求的入参,中间没有模型的参与。两步调用换掉一次幻觉。
重命名也是同一个思路:rename_preview 先返回一个事务 ID 和将要改动的全部位置,确认后再 rename_apply,中途文件被改过就直接拒绝(stale-edit 保护)。不给它「一步到位改 87 个文件」的机会。
另外两个设计
输出是压缩过的。 LSP 的原始返回能把上下文瞬间撑爆------一个常用函数的 references 几百条起步。所以服务端先过滤、去重、分组、截断(默认最多 200 条),再吐给模型,还能切成 markdown 格式直接读。
同一个二进制既是 MCP server 又是 CLI。
bash
vv-mcp fix src/main.ts # 应用并保存安全的 LSP 修复
vv-mcp lsp --operation document_symbols --uri /abs/path/src/main.ts --query handleClick
vv-mcp lsp --operation hover --uri /abs/path/src/main.ts --line 42 --character 17
不带子命令就是 MCP server(客户端这么启动它),带子命令就跑一次请求退出。
而且 CLI 模式不要求你开着 Neovim。 找不到已注册的实例时,它会为目标项目拉一个托管的 headless Neovim 来回答,空闲 15 分钟自动退出;等你真的打开 Neovim 编辑这个项目,托管实例会在 15 秒握手后主动让位给交互实例。
所以 hook、CI、任何一段 shell 都能用上 LSP,不需要 MCP 协议,也不用先把编辑器开起来------下一节里的自动格式化 hook 就是这么接的。
五、编排:把重复动作固化成 Hook
Skill 管的是「怎么想」,Hook 管的是「每次都必须做的事」。
前面散着提了一堆文件,先把位置对齐一下(Claude Code 的全局配置都在 ~/.claude/ 下):
text
~/.claude/CLAUDE.md 全局常驻规则,每次对话都加载
~/.claude/settings.json 权限 + hook 注册 + statusline
~/.claude/hooks/ hook 脚本本体
├── deny-compound-bypass-ast.ts PreToolUse:AST 解析 Bash 命令
├── post-write-code.ts PostToolUse:ESLint + LSP 格式化
└── lib/ tree-sitter 引擎、shell 拆解、路径判断
~/.claude/skills/<name>/SKILL.md skill
~/.config/opencode/opencode.jsonc OpenCode 的对应物(MCP / 权限 / formatter)
项目级的放 <项目>/.claude/,同名规则覆盖全局。settings.json 长这样(省掉了 statusline、editorMode 这些跟本文无关的):
json
{
"permissions": {
"allow": [
"Bash(*)",
"Read(*)",
"Edit(~/tmp/**)",
"mcp__search-mcp__*",
"mcp__figma-mcp__*",
"mcp__context7-mcp__*",
"mcp__ref-mcp__*",
"mcp__gh-grep-mcp__*",
"mcp__db-mcp__*",
"WebSearch",
"WebFetch(*)"
],
"defaultMode": "bypassPermissions"
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Read",
"hooks": [
{ "type": "command", "command": "bun run ~/.claude/hooks/deny-compound-bypass-ast.ts" }
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{ "type": "command", "command": "bun run ~/.claude/hooks/post-write-code.ts" }
]
}
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "bash ~/.zsh/notify/main.sh 'Claude Code'" }] }
],
"PermissionRequest": [
{ "hooks": [{ "type": "command", "command": "bash ~/.zsh/notify/main.sh 'Claude Code needs you'" }] }
]
}
}
PreToolUse:把内建权限系统整个换掉
注意上面那个 "defaultMode": "bypassPermissions" 加 "Bash(*)"。
我把 Claude Code 自带的权限系统关了。
不是图省事,是因为它按命令前缀匹配,只看第一个词------cd /other/repo && git push 这种复合命令直接绕过去。我自己被这个坑过好多次。
所以改成:内建的全放行,然后用一个 PreToolUse hook 自己解析命令来拦。现在这个 hook 走 tree-sitter 解析 AST,不是正则------语法树上的每个命令节点都会被拎出来单独过一遍,不管它藏在 &&、;、管道、$(...) 还是子 shell 里。
这块展开讲挺长的,我单独写了一篇:《一个 Hook 堵住 Claude Code、Codex 的大部分权限「漏洞」》
PostToolUse:自动格式化
每次 Write / Edit 之后自动跑 ESLint 和 Neovim LSP 修复。
另外我关闭了一些 lint 规则,防止 AI 改动后调用 hook 格式化陷入无限循环
js
Bun.spawnSync([
eslint,
'--fix',
'--fix-type',
'layout,suggestion,directive',
'--rule',
'unused-imports/no-unused-imports: off',
'--rule',
'unused-imports/no-unused-vars: off',
'--rule',
'prefer-const: off',
filePath,
])
AI 是分步写代码的。 它这一轮先加了 import,下一轮才写用到它的函数;这一轮先声明 let,下一轮才写重新赋值的分支。
自动格式化如果这时候把「没用到的 import」删了、把 let 改成 const,下一轮它就得回头再改一遍,来回打架。
所以这三条必须关。--fix-type 也只留 layout,suggestion,directive,不动结构。
另外它同时喂给 vv-mcp fix,自动修复代码:
ts
Bun.spawnSync(['vv-mcp', 'fix', filePath], { cwd, stdout: 'ignore', stderr: 'inherit' })
好处是格式化结果跟我在编辑器里手动保存完全一致,而且不限于 JS/TS------任何配了 LSP 的语言都覆盖到。
Stop / PermissionRequest:跑完喊我一声
text
Stop → 桌面通知「Claude Code」 任务干完了
PermissionRequest → 桌面通知「Claude Code needs you」 卡住了要你确认
这两个是幸福感提升最大的配置,成本却几乎为零。 长任务丢给它之后可以去干别的,不用盯着终端。
六、别忘了审查代码
代码写完能不能用?
/code-review 我要求它按六个维度过,不许说「代码写得很棒」这种废话:
text
1. 重复代码 相同或相似的逻辑在多处出现
2. 逻辑冲突 存在矛盾或互相抵消的逻辑
3. 多余逻辑 不必要的计算、判断或冗余代码
4. SRP 违反 一个函数/类做了太多事情
5. 模块耦合 依赖关系过于复杂或紧耦合
6. 硬编码/死代码 魔法数字、未使用的代码、永不执行的分支
用 🔴严重 / 🟡警告 / 🟢建议 分级,没有红灯才能过。而且只观察分析,不修改代码直到我确认。
/debug 则遵循一条原则:不猜测,只验证。
很多 AI 看到报错就开始猜「可能是版本问题?可能是环境问题?」然后让你试一堆没用的命令。所以流程写死:分析代码 → 信息不够就主动问我要(环境变量、依赖版本、日志)→ 还不确定就加日志让我重跑 → 拿到输出再修。
这个 skill 还有个重型模式:起一个本地日志服务器 + 往代码里插埋点,专门抓偶现和跨端时序问题。但入口第一段就是分诊------读代码、跑测试、浏览器自动化能定位的,不许起服务器。
不然每个小 bug 都要走一遍「起服务 → 埋点 → 让我复现」,比 bug 本身还烦。
七、上下文满了,别清空,做交接
上下文窗口再大也有限。更糟的是 Lost in the Middle------上下文越长,注意力越涣散。
当它开始车轱辘话,或者忽略你刚说的要求,就该重置了。
但别直接清空,那得把需求再讲一遍。用 /summary 生成一份交接文档:
markdown
### 1. 背景与目标
### 2. 当前进度与现状
**特别说明**:如果当前代码有逻辑错误、编译失败或运行异常,
请详细说明现状及你认为的原因
### 3. 重要文件引用(3-5 个,别全扔进来)
### 4. 待办事项与下一步
第 2 节里那句「有异常就说明现状和原因」是我后来加的------不加的话它交接时特别爱粉饰太平,把一堆挂着的测试写成「已完成」。
生成完点 New Chat 贴进去,无缝接上。
附录:六个名词一次讲清
配之前得先分清这几个东西,很多人配了半天其实没搞明白区别。
| 术语 | 本质 | 传参 | 自动触发 | Token 消耗 | 用在哪 |
|---|---|---|---|---|---|
| Rules | 常驻提示词 | --- | ✅ | 低 | 全局规范、项目约定 |
| Commands | 可调用提示词 | ✅ | ❌ | 无 | 常用任务、重复操作 |
| Skills | SOP / 领域知识 | ❌ | ✅ | 低 | 标准流程、专业技能、脚本引用 |
| Hooks | 生命周期钩子 | --- | ✅ | 无 | 自动化、质量卡口 |
| Agents | 子代理 | --- | ✅ | 低 | 任务分解、隔离上下文 |
| MCP | 外部工具调用 | ✅ | ✅ | 高 | 本地实现不了的复杂能力 |
几个容易混的点:
Rules vs Commands ------ 本质都是提示词。区别是 Rules 常驻,Commands 敲 / 才调用、能传参。各家 Rules 文件名不同,通用的是 AGENTS.md(Claude Code 是 CLAUDE.md,Gemini 是 GEMINI.md)。
Commands vs Skills ------ Skills 能被自动触发 ,你不用敲 /,AI 看 description 自己判断该不该用。所以 description 是 Skill 的命门,写不好就永远不会被调用。
MCP 的代价 ------ 启动时会把所有工具的描述、入参、出参全加载进上下文,装得越多烧得越狠。第一节那份七个 MCP 的清单就是这么删出来的:能用 CLI 和 Skills 解决的就别上 MCP。
写 Skill 的几条硬规矩
| 检查项 | 要求 |
|---|---|
name |
≤64 字符,小写 / 数字 / 连字符 |
description |
非空、≤1024 字符,第三人称,写清做什么 和何时用 |
SKILL.md 行数 |
建议 <500 行 |
| 渐进式披露 | 主文件放要点,细节丢 references/,脚本丢 scripts/ |
| 引用层级 | 只准一层 |
「渐进式披露」这条最关键:主文件会被全量读进上下文,细节文件只在需要时才读。 一股脑全写主文件,等于每次对话都白烧几千 token。
抄作业
全部配置都在 beixiyo/dotfiles 里,skill 在 .claude/skills/ 下,复制到 ~/.claude/skills/<name>/SKILL.md 就能用。
除了正文讲过的那几个,还有这些,一句话一个:
| Skill | 核心那一条 |
|---|---|
| search | context7 → gh → gh-grep → Exa → Web Search,五级路由,不许上来就 Google |
| github | gh api 全程 --jq 过滤,文件走 base64 -d;禁止任何修改类命令 |
| feasibility | 先出报告再动手,结论只能是 ✅/⚠️/❌,没等到「开工」不准写业务代码 |
| how-to-test | 给一条能跑的命令 + 预期数字;测不出信号就明说「这个不值得测」 |
| research | 先给结论再搭概念地图;证据分五级,官方文档 > 源码/release note > 主流项目用法 > Issue/PR > 博客 |
| code-review | 六维度 + 🔴🟡🟢 分级,只分析不改代码 |
| debug | 不猜测只验证;静态排查能定位就不准起日志服务器 |
| summary | 交接文档,编译失败和挂着的测试必须写清现象和复现方式 |
| workflow | 并行前先算「写入集合」,不重叠才允许并行写;重叠就只读并行、主 agent 串行落写 |
| invoke-plan | 只在跨多轮维护进度文件、要分阶段验收时才用 |
| commit | 先 git log --oneline -10 对齐本仓库的语言和风格,不擅自 git add |
| vim-debug | 不 print,dump 到 /tmp + vim.inspect;瞬态 bug 用 once=true autocmd + defer 200ms |
| playwright-cli | open 必带 --persistent;snapshot 拿 ref 再操作,别让它猜选择器 |
用 Codex、OpenCode 的,我写了个 ai-sync 一键同步过去:
bash
npm i -g @jl-org/ai-sync
ai-sync
它怎么把 Claude 的 skill 转成各家格式,我单独写过一篇:《一次配置,同步到七个 AI CLI》