我的 AI 工作流写了两年,直到 Opus 4.8 才真正生效

我的 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 内建通用搜索 上面全挂了,或者要今天刚出的新闻

ghgh-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 字段之外还挂着一堆 _linksgit_urldownload_url

Windows 用户注意,应该没有 jq 工具,Unix-Like 系统就自己装一下

顺带记住 gh api <endpoint> --jq '.field' 这个万能句式------GitHub 所有 REST 端点都能这么调,languagesreleasestagspulls 换个词就行,不用为每类信息记一条专用命令。

skill 里还有两条约束:包含 ?{} 的 URL 必须加引号(不然 shell 先给你报错);禁止执行任何修改类命令 ------gh repo editgh 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 了。

/playwright-skill

换的原因很简单: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:tsserverrust-analyzergoplspyright 写一遍,所有编辑器都能用;编辑器也不用为每种语言各写一套分析器。

所以我写了 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》

相关推荐
落子AI5 小时前
智谱GLM-4.5编程智能体深度实测:355B MoE架构如何重塑AI编程体验
大模型·ai编程·智能体·glm-4.5·ai工具推荐
晴天小庭6 小时前
介绍下本人开发的OpenCode开源多模态插件——analyze-image
openai·ai编程
唐老板6 小时前
Meta Muse Code 发布:低价杀入编程
ai编程
xcLeigh7 小时前
编程语言的 AI 友好度排名:Python、JavaScript、TypeScript 谁更适合 AI 辅助
javascript·人工智能·python·ai·typescript·ai编程
寒蝉1289 小时前
给 Kimi Code 装个「任务完成提醒」,再也不怕回来才发现任务早跑完了
ai编程
lifallen9 小时前
Emdash 拆解:多 Agent 并行开发桌面端的实现思路,兼谈 ACP 与 A2A
人工智能·学习·ai·ai编程
南方程序猴11 小时前
我把 Node.js、npm 和 Codex CLI 全卸了,重新测了一遍 Windows 一键安装
人工智能·gpt·ai·ai编程
南方程序猴11 小时前
Windows一键安装Codex所有环境
人工智能·gpt·ai·chatgpt·ai编程
智驾11 小时前
API Error: 400 messages...... 解决方案
ai·claude·kimi