我现在的日常是三个 AI 编程工具轮着用Codex 写代码冲劲儿大Claude Code 在复杂项目里更稳Hermes 适合跑自动化和流程类任务。工具换得勤问题也跟着来了——每隔一段时间就要重新给其中某个工具配一套搜索网页、抓取正文的工具脚本配置方式还不一样维护成本直接起飞。直到我把技能文件统一到 SKILL.md 这套约定上一份文件同时被三个工具加载才彻底告别重复劳动。这篇内容就是我从三套技能互相打架到一份 SKILL.md 走天下的完整记录重点讲清楚目录怎么设计、脚本接口怎么做取舍、SKILL.md 怎么写才不会被模型忽略以及三个工具之间到底埋了多少坑。适合跟我一样同时用多个 AI 编程工具、想统一技能资产的人参考。1. 为什么是 SKILL.md三个工具的技能机制里其实藏着同一个标准1.1 三种工具加载额外能力的方式先说个背景。Codex 定位是最纯粹的 AI 编程代理习惯在终端里直接干活特别适合给出一个改动目标让它自己拆解步骤Claude Code 是一个完整的 agent 式 CLI多文件项目渐进式修改是它的强项我在重构老项目时基本都会切到它Hermes 这类智能体框架则更偏任务编排工具调用、流程串联、定时任务都能管。三个工具的侧重点完全不同我一开始也以为技能机制得各写各的结果把三份文档翻完之后发现它们对技能/工具包的支持底层都收敛到了同一个规范Agent Skills。这个规范的核心约定其实很简单一个技能就是一个目录目录里放一个 SKILL.md 文件文件头部用 YAML frontmatter 写技能的 name 和 description正文部分用 Markdown 写模型应该怎么调用这个技能旁边可以附带脚本、模板、参考文档等资源文件。模型通过读取 SKILL.md 来判断技能能不能解决当前任务、该怎么调用。三个工具虽然底层模型不同、调用细节不同但都接受了这套描述结构。我整理了三个工具在实际加载技能时的差异工具技能目录位置入口文件附带资源Codex.codex/skills 或全局 skills 目录SKILL.mdscripts/、references/ 等Claude Code.claude/skills 或用户级 skillsSKILL.mdscripts/、references/ 等Hermes配置文件中指定 skills_pathSKILL.mdscripts/、resources/ 等表格列出来就能看到真正的共性不只是文件名叫 SKILL.md而是frontmatter Markdown 正文 附属资源这套组合拳。这意味着只要我写出的 SKILL.md 足够通用理论上就能被三套环境同时识别。这个发现直接决定了后面所有设计所有技能资产只维护一份工具层只做路径映射不做功能重写。1.2 为什么先做搜索抓取通用技能这个方向确定之后我第一个落地的就是搜索抓取。选它不是因为简单而是因为它最能验证整个机制的可靠性。第一个理由是使用频率实在太高。写代码要查最新接口文档做竞品分析要读官网写周报要找行业资讯AI 如果只会用训练数据里的旧知识很容易给出过时方案。一个能实时搜索、能抓正文的技能几乎每天都会用到。第二个理由是边界足够清晰。搜索的输入是关键词输出是标题 URL 摘要列表抓取的输入是 URL输出是干净的 Markdown 正文。这种输入输出都很明确的工具最适合做成脚本调用模型不需要做复杂推理只要按固定格式执行命令并解析结果就行。第三个原因是验证成本低。如果在 Codex、Claude Code、Hermes 三个工具上都能把搜索抓取跑通那说明一份 SKILL.md 走天下的模式是成立的以后再扩展其他技能只要照同一套约定复制目录就行。所以这个项目表面上是在做搜索抓取实际上是在验证整个通用技能方案是否可行。2. 技能目录怎么设计一份 SKILL.md 走天下的骨架2.1 目录树的最终形态技能目录我放在了统一位置 ~/.agent-skills/下面分成 web-search 和 web-fetch 两个独立子目录~/.agent-skills/ ├── web-search/ │ ├── SKILL.md │ ├── scripts/ │ │ └── search.py │ └── docs/ │ └── provider.md └── web-fetch/ ├── SKILL.md ├── scripts/ │ ├── fetch.py │ └── requirements.txt └── docs/ └── FAQ.md这个结构看起来简单每个位置都是经过权衡的。为什么不直接塞进某个工具的专属目录因为那样会导致为 Codex 配一份、为 Claude Code 再配一份、为 Hermes 还得配一份回到重复劳动的老路。统一放在 ~/.agent-skills/ 之后三个工具都只需要做一次路径映射。scripts/ 和 docs/ 目录的存在也很重要脚本必须和说明文件分离避免 SKILL.md 正文里出现大段代码否则模型加载技能时会消耗过多上下文窗口。另外我在每个技能目录里额外放了一个 docs 子目录。搜索技能里存 provider.md记录不同搜索后端的配置方式和 key 的获取方法抓取技能里存 FAQ.md记录日常使用中遇到的页面解析问题和解决办法。这些文档不是给模型看的是给我自己和其他协作者看的。技能设计得再通用过两个月也会忘掉细节有文档兜底比什么都强。2.2 技能发现机制与路径约定的差异技能发现机制是我面临的第一个兼容性坑。三个工具虽然都用 SKILL.md但扫描路径并不一样。Codex 会在当前项目目录下查找 .codex/skills同时也会读取全局技能目录Claude Code 分项目级和用户级项目级是 .claude/skills用户级放在配置目录Hermes 的目录名不固定而是通过 config 文件里的字段来指定。如果我为每个工具单独复制一份技能目录改一个 bug 就得同步三次早晚会漏。所以我选择了软链接方案mkdir -p ~/.codex/skills ln -s ~/.agent-skills/web-search ~/.codex/skills/web-search ln -s ~/.agent-skills/web-fetch ~/.codex/skills/web-fetchClaude Code 的用户级 skills 目录也做类似软链接Hermes 则直接在配置里把 skills_path 指向 ~/.agent-skills。这里埋了一个极易踩的坑不同工具调用脚本时进程的工作目录可能是项目根目录、用户主目录甚至临时目录。如果脚本里用了相对路径十有八九会在某个工具上找不到目标文件。所以我在设计脚本时就立了一条规矩所有路径都通过file推导绝对路径绝不依赖当前工作目录。这句看起来不起眼后面省了我大量排查时间。2.3 搜索和抓取到底该拆还是该合这是我在目录设计阶段纠结最久的问题。搜索和抓取经常成对出现比如先搜出相关链接再抓取第一篇文章的正文乍一看合成一个 skill 更省事模型调度也更顺滑。但实际测试下来拆开反而更好用。原因主要是技能触发机制。模型判断该不该调用技能时核心依据是 SKILL.md 里的 description。如果把搜索和抓取合成一个技能description 里必须同时描述两种能力语义向量会被拉散。模型在面对一个明确的抓取这个 URL请求时反而可能因为描述里有一半内容是搜索而降低触发置信度。实测数据也印证了这一点合并版本里抓取请求的触发率明显低于独立版本。另外独立技能的错误处理也更简单。如果抓取失败了AI 不需要重新加载整个技能可以直接用搜索技能的结果再找替代链接。两个技能之间是松耦合关系我在 search 的 SKILL.md 里写了一句话如果用户接下来要阅读某个搜索结果可调用 web-fetch 技能抓取正文。这样模型在连续任务中能自然串联两个技能又不至于因为耦合太紧互相拖累。3. 脚本接口是兼容性核心命令行约定与 JSON 输出3.1 接口设计的三个原则SKILL.md 写得好不好决定技能能不能被找到脚本写得好不好决定技能能不能用起来。我给脚本接口定了三条铁律后来三次工具迁移都没有改过。第一纯命令行调用无交互、无 TUI。AI 工具执行脚本的方式是发起一个进程、等待输出它没法跟脚本做交互式问答。所有参数必须通过命令行 flag 传入所有结果必须一次性输出。第二输入输出用结构化格式输出尽量用 JSON。模型解析 JSON 的可靠性远高于解析自由文本这能显著降低把结果用错的概率。第三依赖缺失时不能静默失败要给出明确报错。脚本挂了不要紧模型可以根据报错调整策略但如果脚本假装成功却输出空结果模型会被误导整个任务就会跑偏。之所以把这三条当成铁律是因为我见过太多SKILL.md 写得很好、脚本一跑就挂的例子。最常见的死法是脚本里用 input() 等待用户输入在终端直接挂起其次是脚本把调试日志和结果混在一起输出模型把日志当正文还有的是脚本依赖第三方包没装每次调用都抛异常。这些问题的根源都是没有用模型会怎么调用脚本的视角来设计接口。3.2 search.py 的参数设计与返回结构搜索脚本的职责是接收关键词返回若干条搜索结果。接口定义如下python3 scripts/search.py --query Agent Skills 规范 --top-k 5 [--engine auto]--query 是必填参数--top-k 控制返回条数默认 5 条--engine 用来指定搜索后端默认 auto。脚本返回的 JSON 结构如下{ status: ok, engine: auto, query: Agent Skills 规范, total: 5, results: [ { title: Agent Skills 入门指南, url: https://docs.example.com/agent-skills, snippet: Agent Skills 让模型通过 SKILL.md 文件加载额外能力... } ] }这里的核心取舍是搜索后端怎么选。靠谱的方案其实有几种接入云搜索 API优点是返回结果干净、自带摘要、格式稳定缺点是得申请 key、可能有调用限额自建搜索实例适合对隐私和可控性要求高的团队但对个人用户来说维护成本偏高直接解析公开搜索页面的 HTML优点是零依赖零成本缺点是页面结构经常变、脚本需要维护而且某些站点对非浏览器请求有限制。我最后选择了API 优先 公开页面解析兜底的双层策略search.py 启动时先读环境变量里的 SEARCH_API_KEY有 key 就走 API没有 key 就退回解析公开搜索页面。这样技能在个人电脑、团队服务器、容器环境里都不会死。下面是一个简化版的实现骨架#!/usr/bin/env python3 import argparse, json, os from urllib.parse import quote_plus API_KEY os.environ.get(SEARCH_API_KEY, ) def search_with_api(query, top_k): # 接入具体搜索服务的逻辑按服务方要求的格式组装请求 return [] def search_with_html(query, top_k): import urllib.request url fhttps://search.example.com/search?q{quote_plus(query)} req urllib.request.Request(url, headers{User-Agent: Mozilla/5.0}) html urllib.request.urlopen(req, timeout10).read() # 解析 title / url / snippet 的逻辑略按实际页面结构调整 return [] def main(): parser argparse.ArgumentParser() parser.add_argument(--query, requiredTrue) parser.add_argument(--top-k, typeint, default5) parser.add_argument(--engine, defaultauto) args parser.parse_args() results search_with_api(args.query, args.top_k) if API_KEY else search_with_html(args.query, args.top_k) print(json.dumps({status: ok, query: args.query, total: len(results), results: results}, ensure_asciiFalse)) if __name__ __main__: main()注意我特意用了 ensure_asciiFalse这个细节很重要。如果 JSON 里的中文被转成 \uXXXX模型读取结果的时候等于先要做一轮翻译既费 token 又容易出错。直接输出中文字符模型的阅读效率会高很多。3.3 fetch.py 的抓取与正文提取逻辑抓取脚本比搜索简单但坑反而更深。简单在于输入就是一个 URL复杂在于把网页正文干净地提取出来这件事远比想象中麻烦。网页里通常混着导航、侧边栏、评论区、广告位、相关推荐如果直接把整页 HTML 转成 Markdown输出会是一大坨噪音。模型读得慢不说还容易被无关内容干扰判断。所以 fetch.py 的核心不是下载而是正文提取。我用的主力库是 trafilatura这个库专为网页正文提取设计内置很多启发式规则对新闻、文档页、博客的主内容识别相当准还原生支持输出 Markdown。如果环境里没有 trafilatura脚本会自动降级到 BeautifulSoup html2text 的组合先把 script、style、nav、footer、aside 标签全部拆掉再对剩余 HTML 做正文转换。降级方案效果略差但至少不会让脚本崩溃。fetch.py 的接口是python3 scripts/fetch.py --url https://docs.example.com/agent-skills/overview返回结果{ status: ok, url: https://docs.example.com/agent-skills/overview, title: Agent Skills 概览, content: # Agent Skills 概览\n\nAgent Skills 是..., word_count: 2860 }脚本里我加了几条硬性约束。网络请求必须带浏览器 User-Agent否则很多站点直接返回 403HTML 解码用 utf-8 errorsignore因为不少站点的编码声明和实际内容不一致忽略错误比抛异常稳健正文长度设上限单次抓取最多保留 5 万字符防止超大页面把模型上下文撑爆。3.4 不同环境的降级与容错功能可以弱但不能崩是我写这个脚本的第一原则。因为不同工具的运行环境差异太大尤其是 Hermes 这种可能部署在容器里的框架环境里缺什么包完全是未知数。我做了三层降级。第一层Python 依赖不全时自动切换提取库trafilatura 不在就用 BeautifulSoup两个都不在就返回原始 HTML 转纯文本并在结果里标注提取质量。第二层网络请求失败时重试并切换策略第一次超时退避两秒重试一次返回 403 就换 User-Agent 再试最终失败时返回明确的 error 状态。第三层如果连 Python 环境都没有我准备了一个 shell 版脚本兜底用 curl 抓页面、用 sed 做粗糙的标签清洗。效果确实粗糙但模型至少能拿到网页源码不会完全束手无策。这套降级逻辑写起来确实费事但收益很大。技能被三个工具共享之后实际运行环境数量翻了三倍任何环境问题都会被放大。预先做好降级等于把所有环境差异挡在脚本之外。4. SKILL.md 怎么写才能被三个工具看懂4.1 YAML frontmatter 的兼容写法SKILL.md 的入口信息是开头的 YAML frontmatter最核心的是 name 和 description 两个字段。name 要短、要语义化。我用的是 web-search 和 web-fetch而不是 search_and_fetch_tool_v2 这种绕名字。短名字方便模型记忆也能减少在上下文里占用的 token。description 则需要精心设计我踩过最深的坑就是把 description 写太长把使用说明、错误码、注意事项全塞进去结果三个工具都不怎么触发这个技能。后来我把 description 统一成两句话模板第一句英文说明能力和使用时机第二句中文做同义补充。以 web-fetch 为例--- name: web-fetch description: Fetch a web page and extract main content as Markdown. 用于抓取网页并提取正文为 Markdown 格式适合阅读在线文档、新闻、博客等网页内容。 ---为什么中英混写因为不同模型对语言的敏感性不同。Codex 对英文指令的理解更稳定Claude Code 中英文都能处理Hermes 这类智能体框架对中文描述的匹配度更高。中英各一句两边都能踩中又不至于让 description 变得臃肿。4.2 正文部分必须写清楚的三件事SKILL.md 的正文是给模型看的操作手册不是给人看的文档要尽量减少模型自行发挥的空间。我总结下来必须写清楚三件事。第一什么场景下使用。给模型明确的触发条件比如当用户要求打开一个网页、阅读链接内容、将网页转为 Markdown 时使用本技能。第二具体怎么调用。直接把命令行写出来并附上参数示例。模型在生成调用时会高度模仿示例里的写法所以示例必须保证准确。第三什么时候不要用。这条最容易被忽略但对抓取类技能尤其重要。我会明确写不适用于需要登录认证的页面不适用于本地文件路径不用于绕过任何访问限制。有了这条边界模型在复杂多轮对话中才不会跑偏。另外我会加一节输出处理建议告诉模型脚本输出的是 JSONcontent 字段可能很长建议先保存到临时文件再继续处理避免把大段文本直接塞进上下文导致后续任务被冲淡。这把模型的注意力引导到正确方向同时避免上下文被截断。下面是一份完整的 web-search SKILL.md 示例--- name: web-search description: Search the web for given keywords and return a list of results. 用于根据关键词搜索网络内容返回标题、链接和摘要列表。 --- # Web Search 技能 当用户要求搜索资料、查询最新信息、查找文档或需要实时网络内容时使用本技能。 ## 使用方式 执行以下命令搜索 python3 ~/.agent-skills/web-search/scripts/search.py --query 关键词 --top-k 5 脚本输出为 JSON字段说明 - status: ok 表示成功 - results: 搜索结果数组每一项包含 title、url、snippet 三个字段 - total: 实际返回的结果条数 ## 注意事项 - 搜索关键词建议使用原文语言如需中英混合查询可以保留原词 - 如果搜索结果为空可以尝试减少 --top-k 值或者换一种关键词表达 - 搜索后的网页正文阅读请调用 web-fetch 技能 ## 工具适配说明 - 在 Claude Code 中建议先搜索后抓取形成检索到阅读的任务链路 - 在 Codex 中搜索结果可以先整理为 Markdown 列表再粘贴到对话减少模型解析成本 - 在 Hermes 中脚本无参数调用时需输出帮助文本便于工具发现4.3 给每个工具留一条侧门共享 SKILL.md 不等于三个工具的行为要完全一致每个工具都有自己的使用习惯。我在 SKILL.md 末尾专门加了一节工具适配说明给每个工具留出侧门。在 Claude Code 中如果同一轮任务里既需要搜索又需要抓取建议先执行搜索再到结果里挑选 URL 执行抓取避免一次性向模型塞入过多信息。在 Codex 中为了减少重复抓取抓取结果可以先缓存到 /tmp/web-fetch-cache/ 目录用 URL 的哈希作为文件名下次遇到相同 URL 直接读缓存。在 Hermes 中脚本无参数调用时要输出帮助文本因为 Hermes 的工具发现机制会主动读取脚本帮助信息。这些提示写在同一份文件里不影响其他工具的正常使用但对各工具的体验提升很明显。核心思路就是协议相同、策略分叉——所有工具共享同一个接口但各自的使用策略可以写进 SKILL.md 里分别引导。5. 实操记录从零让三个工具跑通同一套 skill5.1 建目录并落地两个核心脚本整个部署流程其实很短。先建目录mkdir -p ~/.agent-skills/web-search/scripts mkdir -p ~/.agent-skills/web-fetch/scripts然后写 fetch.py。网络请求部分我直接用一个带浏览器 User-Agent 的请求头目标 URL 通过命令行参数传入。为了控制输出大小抓到的正文会截断到 5 万字符以内。核心逻辑如下#!/usr/bin/env python3 import argparse, json, sys from urllib.request import Request, urlopen def convert(html): try: import trafilatura return trafilatura.extract(html, output_formatmarkdown) except ImportError: from bs4 import BeautifulSoup import html2text soup BeautifulSoup(html, html.parser) for tag in soup([script, style, nav, footer, aside]): tag.decompose() return html2text.html2text(str(soup.find(body) or soup)) def fetch_and_convert(url): req Request(url, headers{User-Agent: Mozilla/5.0}) with urlopen(req, timeout15) as resp: html resp.read().decode(utf-8, errorsignore) content convert(html) return { status: ok, url: url, content: content.strip()[:50000], word_count: len(content.split()) } if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--url, requiredTrue) args parser.parse_args() try: print(json.dumps(fetch_and_convert(args.url), ensure_asciiFalse)) except Exception as e: print(json.dumps({status: error, message: str(e)}, ensure_asciiFalse), filesys.stderr) sys.exit(1)这里的几个细节都是实操验证过的。User-Agent 必须设置否则抓文档站频繁吃 403json.dumps 用 ensure_asciiFalse不然中文正文全变成 \uXXXX 转义模型读起来费劲异常信息写到 stderr 而不是 stdout确保 stdout 只有标准 JSON。这些习惯后来在其他技能脚本里也沿用下来。5.2 编写一份完整可复用的 SKILL.md脚本就位后下一步是 SKILL.md。我在第 4 节已经贴过 web-search 版本fetch 版本结构类似唯一的区别是 commands 段里只放 fetch 命令description 也更聚焦在抓取网页正文这个动作上。我在实际编写时遵循一个原则正文不要写流程性的废话直接给命令、给字段、给边界。模型加载 SKILL.md 时会把它当作上下文的一部分内容越精炼留给真正内容的 token 就越多。所以每句话都要有明确的信息量能一行写完的绝不写两行。5.3 在三个工具里完成注册注册环节我按工具分开处理。Codex 最快把技能目录软链到 Codex 的用户级技能目录就行mkdir -p ~/.codex/skills ln -s ~/.agent-skills/web-search ~/.codex/skills/web-search ln -s ~/.agent-skills/web-fetch ~/.codex/skills/web-fetchClaude Code 分项目级和用户级。如果想全局生效就在用户级 skills 目录放软链如果只想在某个项目里使用直接建 .claude/skills 目录再复制或软链过去即可。Hermes 的配置方式最灵活它通过配置文件里的 skills_path 指定技能目录直接把该字段指向 ~/.agent-skills 就行。注意 Hermes 会扫描目录下所有含 SKILL.md 的子目录所以指向父目录就能同时识别 web-search 和 web-fetch 两个技能。注册看起来是三个工具三套流程但只做一次。之后新增技能无非是建目录、写 SKILL.md、放脚本、配一次路径几分钟搞定。5.4 三端联调同一任务各跑一遍配置完成后我让三个工具分别执行同一个任务搜索 Agent Skills 规范相关文档并抓取排在第一位的链接。Claude Code 的执行流程最顺滑读取 web-search 描述 → 执行搜索脚本 → 拿到结果 → 读取 web-fetch 描述 → 执行抓取。一气呵成甚至没有额外提问。Codex 的执行略有不同它会先把搜索和抓取拆成行动计划再逐步执行这是 Codex 对工作流的拆解习惯。好处是能清晰看到它准备干什么坏处是步骤偏多。Hermes 的执行速度最慢因为它多了一层工具发现机制要先扫描技能目录、读取 SKILL.md、再决定调用但它对中文描述的匹配最好能准确把搜一下加上抓取正文拆分成两个脚本调用。三端跑通之后我对这套模式的信心就建立起来了。代码没有改一行纯靠统一的目录和 SKILL.md 约定三个工具都能准确使用同一套搜索抓取能力。6. 跨工具技能文件的坑六个实测问题与排查方案6.1 问题一description 太泛模型不触发最早的版本里我把 description 写成了搜索和抓取网页工具支持关键词搜索、网页正文提取、结果格式化、缓存管理、多引擎切换等。听起来很全能但三个工具几乎都不主动调用这个技能。排查后发现description 就是模型决定是否使用技能时的核心依据。内容太杂模型对真实意图的匹配置信度就会被稀释。我把 description 压缩成Search the web for given keywords and return a list of results. 用于根据关键词搜索网络内容之后触发率明显提升。这个教训我后来应用到了所有技能文件里description 只写能力边界不写实现细节。6.2 问题二脚本输出带了日志噪音早期的 fetch.py 里我在关键步骤加过 print(正在抓取...) 这类日志。第一次联调就翻车模型把正在抓取...和解析完成当成了正文的一部分输出前多了好几行乱码。解决方案很简单所有诊断信息一律写入 stderrstdout 只保留标准 JSON。模型只读取 stdout日志自然不会污染结果。后来我写任何脚本都默认遵守这条约定不管技能文件给谁用。6.3 问题三工作目录不同导致找不到文件这个坑藏得比较深。有段时间脚本里用了相对路径比如把抓取结果保存到 cache/output.json。在 Codex 里一切正常因为它的工作目录是项目根目录但 Hermes 调用脚本时工作目录是临时目录脚本在临时目录下创建了文件模型在项目目录里当然找不到。解决方法是把路径基准改为脚本自身位置使用 os.path.dirname(os.path.abspath(file))所有文件读写都基于这个绝对路径。这个修正做完之后三个工具的调用表现完全一致了。6.4 常见问题速查表现象可能原因排查与解决技能不触发description 太泛或太长精简为两句话明确使用时机输出格式混乱stdout 被日志污染日志走 stderrstdout 只输出 JSON脚本找不到文件相对路径依赖 CWD用file推导绝对路径抓取返回 403被识别为非浏览器请求设置浏览器 User-Agent必要时重试中文乱码编码声明与实际不一致utf-8 errorsignore 解码抓取为空页面为动态渲染换用无头浏览器方案或改用搜索摘要某工具不识别技能软链接或配置路径错误检查目录扫描路径是否包含技能子目录排查这类问题要记住顺序先看脚本能不能在终端里单独跑通再看模型调用的命令是否正确最后才检查工具配置里的扫描路径。跳过第一步直接看配置往往是在浪费时间。这套方案跑通之后我最大的感受是AI 工具之间的技能互通没有想象中那么难关键是要忍住给每个工具单独定制的冲动。把接口收敛到最小公共子集——脚本输出 JSON、描述精准、路径绝对——就能让 Codex、Claude Code、Hermes 共享同一套搜索和抓取能力。如果以后接入的新工具不支持 Skills 规范那就单独给那个工具包一层适配器核心脚本和 SKILL.md 依然可以复用。最后再分享一个小技巧在技能目录里放一个 tests/ 子目录写几个只读测试用例比如抓取 example.com、搜索一个固定关键词。每次修改脚本后先跑一遍测试比在三个工具里反复人工验证高效得多。这套模式打底之后技能扩展基本就是复制目录、改描述、放脚本的流水线操作了。