{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}

Ast Grep

通过 ast-grep 做 AST 感知的结构化代码搜索与重写。

Skill 元数据

来源可选——使用 hermes skills install official/software-development/ast-grep 安装
路径optional-skills/software-development/ast-grep
版本1.0.0
作者Yeongyu Kim (code-yeongyu), adapted by Hermes Agent
许可证MIT
平台linux, macos, windows
标签ast, codemod, refactoring, structural-search, code-search, rewrite, tree-sitter
相关 skillsimplify-code、systematic-debugging

参考:完整 SKILL.md

INFO

以下是 Hermes 在触发此 skill 时加载的完整 skill 定义。这是 skill 激活时 agent 所看到的指令内容。

ast-grep

ast-grep(二进制也叫 sg)是一个跨 25 种语言的 AST 感知搜索与重写工具。它把你的 pattern 当作代码,用和解析项目相同的方式解析它,然后做结构化匹配。当你的问题取决于代码形状而非文本字节时,它就是对的工具。

本 skill 在 scripts/ast_grep_helper.py 附带一个 Python 封装,并在 install.sh(POSIX)和 install.ps1(Windows)附带平台安装脚本。封装增加了离线 pattern 校验、两遍写入技巧和二进制自动解析。把它作为默认入口。

上游来源:vendored 自 code-yeongyu/ast-grep-skill(MIT),随 oh-my-openagent 的 shared-skills bundle 发布。


何时使用此 skill

当问题关于代码结构而非字节时使用:

  • "找出每个接受 Request 参数的函数。"
  • "把每个 console.log(x) 重写为 logger.info(x)。"
  • "去掉每个 as any 强转。"
  • "在整个仓库里把 require(...) 替换为 import。"
  • "找出空 catch 块。"
  • "把 Optional[X] 迁移为 X | None。"
  • "把这个 codemod 应用到这 200 个文件。"
  • "跑我们的 YAML lint 规则并列出违规。"

当问题是文本形状(字符串字面量内容、注释、license 头、文件名、跨语言 regex)时,切到 search_files(或直接 rg)。拿不准时问一句:"答案取决于语言的语法树,还是只取决于文件字节?"前者用 ast-grep,后者用 search_files。

Hermes 集成注意:

  • 通过 terminal 工具运行 helper 和 sg。每个 pattern 用单引号包起来,让 shell 永远不展开 $VAR。
  • 对于围绕匹配的 find→read 链,用 --json-out 并配合 execute_code 处理,而不是管道给解释器。
  • 它补充(不替代)Hermes 的 patch 工具:patch 用于你亲手写的定点编辑;ast-grep 用于跨多处的 pattern 驱动批量重写。

agent 必须内化的三件事

1. ast-grep 不是 regex

通配符是 $VAR(一个 AST 节点)和 $$$(零个或多个节点)。regex 语法会静默失败:

你写的ast-grep 看到的你想要的
foo\|barfoo 和 bar 的按位或跑两次独立搜索
.*foo无法解析$$$ foo(若 $$$ 是节点列表)或用 rg
\w+无法解析$VAR 捕获任意标识符
[a-z]字符类,无法解析切到 rg

完整反模式表见 references/pitfalls.md §1。helper 的 validate 子命令会机械地捕获这些——在手工调试"无匹配"之前先调它。

2. pattern 必须是合法代码

pattern 本身必须能解析。def $FN($$$): 会失败,因为末尾的 : 让它不完整;用 def $FN($$$)。没有参数/函数体的 function $NAME 会失败;用 function $NAME($$$) { $$$ }。各语言完整表见 references/pitfalls.md §2。

3. --update-all 和 --json 互斥(静默)

这是脚本化时最大的坑。sg run -p P -r R --json --update-all 返回 JSON 但不改动文件。要既预览又应用,跑两遍:

sg run -p P -r R --json=compact .   # 第 1 遍:看会改什么
sg run -p P -r R --update-all .     # 第 2 遍:真正应用

helper 在你调 replace --apply 时自动做这件事。读 references/pitfalls.md §9。


封装脚本——scripts/ast_grep_helper.py

单文件 Python 3 标准库封装。每个 OS 都一样。agent 的默认入口。

search——找 pattern 的所有匹配

python scripts/ast_grep_helper.py search 'console.log($MSG)' --lang ts src/

先离线校验 pattern。如果 pattern 看起来像 regex(\w、.*、| 等),helper 带提示退出,绝不调用 sg——省一次往返。传 --force 跳过校验。

标志:

  • --lang ts(或 25 种语言之一;接受 js、py、rs、kt 等别名)
  • --globs '!**/*.test.ts'(可重复;前缀 ! 表示排除)
  • -C 3(上下文行数)
  • --json-out(原始 JSON 而非人类可读格式)

replace——按 pattern 重写,默认 dry-run

# Dry-run 预览(默认——不改文件)
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/

# 真正应用
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/ --apply

helper:

  1. 校验 pattern 和 rewrite 中可提示检测的错误。
  2. 用 --json=compact 跑第 1 遍收集匹配并展示预览。
  3. 若设了 --apply,用 --update-all 跑第 2 遍改动文件。

scan——跑 YAML 规则

# 从 cwd 发现 sgconfig.yml 并跑所有规则
python scripts/ast_grep_helper.py scan src/

# 跑单个规则文件
python scripts/ast_grep_helper.py scan -r rules/no-console.yml src/

# 应用自动修复
python scripts/ast_grep_helper.py scan -U src/

# CI 友好的 GitHub 注解
python scripts/ast_grep_helper.py scan --report-style short src/

validate——离线 pattern 检查(不调用 sg)

适用于 CI lint、pre-commit 钩子和快速健全性检查:

python scripts/ast_grep_helper.py validate '\w+' --lang ts
# → exit 2: regex \w not supported. Use $VAR for identifiers.

python scripts/ast_grep_helper.py validate 'console.log($MSG)' --lang ts
# → exit 0: pattern looks plausible for ast-grep.

langs / doctor / install

python scripts/ast_grep_helper.py langs       # 列出 25 种支持语言和别名
python scripts/ast_grep_helper.py doctor      # 检查 ast-grep 二进制可用性
python scripts/ast_grep_helper.py install     # 委托给 install.sh / install.ps1

new 和 test 子命令直接代理到 sg new 和 sg test。


直接用 sg(helper 不够用时)

helper 是有主见的。要完全控制就降到 sg。本 skill 在 references/cli.md 附了一张 CLI 速查表。最小惯用法:

# 搜索
sg run -p 'console.log($MSG)' --lang ts src/

# 带 JSON 搜索,用于脚本
sg run -p 'console.log($MSG)' --lang ts --json=compact src/

# 重写,dry-run
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --lang ts --json=compact src/

# 重写,应用
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --lang ts --update-all src/

# 从 stdin 读 pattern(适合临时实验)
echo 'console.log("hi")' | sg run -p 'console.log($MSG)' --lang js --stdin

# 调试一个返回 0 匹配的 pattern
sg run -p '<your pattern>' --lang <lang> --debug-query=ast --stdin <<< '<sample-code>'

# 跑 YAML 规则
sg scan src/

# 内联 YAML 规则(一次性)
sg scan --inline-rules '
id: no-todo
language: TypeScript
severity: warning
rule: { pattern: TODO }' src/

在 shell 里直接用 sg 时,始终单引号 pattern,让 $VAR 不被 shell 展开。


决策树——何时用什么

USER asks for "find/rewrite/codemod"
│
├─ structural pattern (function shape, call, class, import, control flow)
│  └→ ast-grep (this skill)
│
├─ text pattern (regex, alternation, character classes, file names)
│  └→ search_files / rg
│
├─ semantic question (what variable does this refer to? does this throw?)
│  └→ LSP tools, TypeScript compiler, Pyright, Semgrep with type inference
│
└─ multiple repos / federated search
   └→ a search engine + then ast-grep / rg / LSP per-repo

如果用户说"找出所有"或"每一个",当目标是形状化的(函数、类、调用、import、语句)时默认用 ast-grep。当目标是文本(字符串内容、注释、license 头、文件名、标识符子串)时默认用 search_files。


重写时始终先 dry-run

坏 pattern 会静默改错东西。helper 的 replace 默认 dry-run 就是为此。流程:

  1. 搜索确认匹配:helper search '<pattern>' --lang X .
  2. Dry-run 重写:helper replace '<pattern>' '<rewrite>' --lang X .(不加 --apply)
  3. 检查 dry-run 摘要:匹配数、受影响文件、逐处预览。
  4. 错了:细化 pattern,回到第 1 步。
  5. 对了:helper replace '<pattern>' '<rewrite>' --lang X . --apply。

绝不应用你没先 dry-run 过的重写。在 git 仓库里 --apply 后,提交前用 git diff --stat 审查。


当 sg 返回 0 匹配但你知道代码就在那

按优先级:

  1. 跑 helper validate '<pattern>' --lang <lang>——捕获 regex 误用、缺函数体、Python 末尾冒号。
  2. 检查 --lang——sg 从扩展名推断;如果你传一个 .tsx 文件却用 --lang ts(而非 tsx),JSX 解析不了。
  3. 检查解析后的 pattern:sg run -p '<pattern>' --lang <lang> --debug-query=ast --stdin <<< '<sample>'。如果显示 ERROR 节点,pattern 有问题。
  4. 检查目标文件的 AST:sg run -p '$_' --lang <lang> --debug-query=cst path/to/file | head -40——找到你想匹配的 kind。
  5. 试试 playground:<https://ast-grep.github.io/playground.html>——贴代码 + pattern,看发生了什么。

不要盲目换变体重试。每次失败都有原因;把它暴露出来。


何时用 YAML 规则 vs 内联 -p pattern

用内联 -p 当:

  • 一次性临时查询。
  • pattern 简单(无约束、无修复模板)。
  • 你在探索。

用 YAML 规则(放在 rules/ 下的文件,通过 sg scan 跑)当:

  • pattern 会复用(lint 规则、在 CI 里跑的 codemod)。
  • 你需要 constraints、transform、复杂的 inside/has 或组合逻辑。
  • 你想要自动修复(fix: 字段)。
  • 你想测试规则(通过 sg test 做快照测试)。

完整 YAML 规则 schema 见 references/yaml-rules.md。项目配置(sgconfig.yml、ruleDirs、utilDirs)见 references/sgconfig.md。


输出纪律

  • sg run --json=compact 产生匹配对象数组:{ file, range: {start, end}, text, replacement?, lines, language, ... }。
  • 不带 --json,sg 产生适合终端的人类可读彩色输出。
  • helper 默认输出人类可读(file:line:column + 匹配预览)。传 --json-out 拿原始 JSON。
  • helper 的 replace 总会摘要:匹配数、文件数、逐处预览。

向用户摘要时,务必带上受影响文件数,而不只是匹配数。用户关心影响半径。


必读(按优先级)

  1. references/patterns.md——元变量、命名规则、严格度层级。不确定 pattern 为何不匹配时读。
  2. references/pitfalls.md——失败模式实战手册。0 匹配让你意外时读。
  3. references/recipes.md——按语言复制粘贴的 pattern。开新任务时先读。
  4. references/cli.md——sg run、sg scan、sg test、sg new、sg lsp。helper 不够用时读。
  5. references/yaml-rules.md——YAML 规则 schema。内联 pattern 不够用时读。
  6. references/sgconfig.md——项目级配置。为真实项目配 sg scan 时读。
  7. references/install.md——各 OS 安装方法。仅当 install.sh / install.ps1 失败时读。

不变量(不要破坏)

  • 搜索前先校验。 编程生成 pattern 时,先调 helper validate。它捕获占"0 匹配"调试会话约 70% 的 regex 误用类错误。
  • 应用前先 dry-run。 绝不不看匹配就跑 sg run -r ... --update-all。helper 的 replace 默认强制这一点。
  • 两遍写入。 直接用 sg 既预览又应用时,跑两次调用——--json 会忽略 --update-all。
  • shell 里单引号 pattern。 用 '$VAR' 而非 "$VAR"。双引号里 shell 把 $VAR 展开成空串,破坏 pattern。
  • pattern 是代码,不是 regex。 当 pattern 需要 |、.*、\w 或 [a-z] 时,切到 search_files。不要把 ast-grep 硬掰成 regex 形状。
  • stdin 必须给 --lang。 用 --stdin 管道时,显式设 --lang;sg 无法从扩展名推断。
  • Linux:优先用 ast-grep 而非 sg,因为 sg 与 util-linux 的 setgroups 冲突。helper 处理了这一点;如果你直接调 sg,alias 一下:alias sg=ast-grep。