Ast Grep
目次
- skill の情報
- 参考: SKILL.md 全文
- この skill を使う場面
- エージェントが頭に入れておくべき 3 点
- 1. ast-grep は正規表現ではありません
- 2. パターン自体が正しいコードである必要があります
- 3. --update-all と --json は同時に使えません(何も言わずに)
- ラッパー — scripts/ast_grep_helper.py
- search — パターンに一致するものをすべて探す
- replace — パターンで書き換える。既定は下見だけ
- scan — YAML で書いた決まりを走らせる
- validate — 通信せずにパターンを検査する(sg を呼びません)
- langs / doctor / install
- sg を直接使う(ラッパーでは足りないとき)
- 何をいつ使うかの見取り図
- 書き換えるときは、必ず先に下見をする
- コードはあるはずなのに sg が 0 件を返すとき
- YAML の決まりと、その場で書く -p パターンの使い分け
- 出力の扱い方
- 先に読むもの(優先度の順)
- 守ること
ast-grep で構文木を見ながらコードを検索・書き換えします。
skill の情報
| 提供元 | 追加 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 |
| 関連 skill | simplify-code, systematic-debugging |
参考: SKILL.md 全文
ast-grep
ast-grep(実行ファイルの名前は sg でもあります)は、25 の言語にまたがる 構文木を見て検索・書き換えするツール です。渡したパターンをコードとして扱い、プロジェクトのコードと同じやり方で解析して、構造として一致するものを探します。文字の並びではなく コードの形 で答えが決まる場面には、これが向いています。
この skill には、Python のラッパー scripts/ast_grep_helper.py と、環境ごとの導入用スクリプト install.sh(POSIX 系)・install.ps1(Windows)が入っています。ラッパーは、通信せずにパターンを検査する機能、2 回に分けて書き換える手順、実行ファイルの自動判別を足してくれます。まずはこれを使ってください。
元になったもの: code-yeongyu/ast-grep-skill(MIT)を、oh-my-openagent の shared-skills 一式に入っている形のまま取り込んでいます。
この skill を使う場面
文字の並びではなく コードの構造 が問われるときに使います。
- 「
Requestを引数に取る関数をすべて探して」 - 「
console.log(x)をすべてlogger.info(x)に書き換えて」 - 「
as anyのキャストをすべて外して」 - 「リポジトリ全体で
require(...)をimportに置き換えて」 - 「中身が空の catch を探して」
- 「
Optional[X]をX | Noneに移して」 - 「この一括変換を 200 ファイルに当てて」
- 「YAML で書いた検査の決まりを走らせて、違反を出して」
答えが文字の並びで決まるとき(文字列リテラルの中身、コメント、ライセンス表記、ファイル名、言語をまたぐ正規表現)は search_files(あるいは素の rg)に切り替えてください。迷ったら「答えはその言語の構文木で決まるのか、それともファイルの文字の並びで決まるのか」と考えます。前者なら ast-grep、後者なら search_files です。
Hermes と組み合わせるときの注意です。
- ラッパーや
sgはterminalツールから実行してください。パターンは必ずシングルクォートで囲み、シェルに$VARを展開させないようにします。 - 一致した箇所を探してから読む、という流れでは
--json-outを使い、インタープリタにパイプせずexecute_codeで処理してください。 - これは Hermes の
patchツールを置き換えるものではなく、補うものです。patchは自分で書いた狙い撃ちの編集用、ast-grep はパターンで多数の箇所をまとめて書き換える用です。
エージェントが頭に入れておくべき 3 点
1. ast-grep は正規表現ではありません
使えるワイルドカードは $VAR(構文木のノード 1 つ)と $$$(0 個以上のノード)です。正規表現の書き方は、エラーも出ないまま外れます。
| 書いたもの | ast-grep が受け取ったもの | 本当にやりたかったこと | |
|---|---|---|---|
| `foo\ | bar` | foo と bar のビット論理和 |
2 回に分けて検索する |
.*foo |
解析できない | $$$ foo($$$ がノードの並びのとき)、または rg を使う |
|
\w+ |
解析できない | 任意の識別子を取るなら $VAR |
|
[a-z] |
文字クラス。解析できない | rg に切り替える |
外しやすい書き方の一覧は references/pitfalls.md の §1 にあります。ラッパーの validate を使えば機械的に見つかるので、「一致しない」を手で調べ始める前に呼んでください。
2. パターン自体が正しいコードである必要があります
パターンそのものが解析できないといけません。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 を返しますが、ファイルは書き換わりません。下見と適用の両方をしたいなら、2 回に分けて 実行します。
sg run -p P -r R --json=compact . # pass 1: see what would change
sg run -p P -r R --update-all . # pass 2: actually applyreplace --apply を呼べば、ラッパーがこれを自動でやってくれます。references/pitfalls.md の §9 も読んでください。
ラッパー — scripts/ast_grep_helper.py
Python 3 の標準ライブラリだけで書かれた 1 ファイルのラッパーです。どの OS でも同じように動きます。エージェントはまずこれを使ってください。
search — パターンに一致するものをすべて探す
python scripts/ast_grep_helper.py search 'console.log($MSG)' --lang ts src/先に、通信せずにパターンを検査します。パターンが正規表現に見える場合(\w、.*、| など)は、助言を出して終了し、sg を呼びません。往復が 1 回減ります。検査を飛ばしたいときは --force を付けます。
指定できるもの:
--lang ts(対応する 25 言語のいずれか。js、py、rs、ktのような別名も使えます)--globs '!**/*.test.ts'(何度でも指定できます。先頭に!を付けると除外です)-C 3(前後に出す行数)--json-out(読みやすい形ではなく、そのままの JSON を出します)
replace — パターンで書き換える。既定は下見だけ
# Dry-run preview (default — no files mutated)
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/
# Actually apply
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/ --applyラッパーがすること:
patternとrewriteの両方を検査して、助言できる間違いを見つけます。- 1 回目を
--json=compactで走らせ、一致した箇所を集めて下見を出します。 --applyが付いていれば、2 回目を--update-allで走らせてファイルを書き換えます。
scan — YAML で書いた決まりを走らせる
# Discover sgconfig.yml from cwd and run all rules
python scripts/ast_grep_helper.py scan src/
# Run a single rule file
python scripts/ast_grep_helper.py scan -r rules/no-console.yml src/
# Apply auto-fixes
python scripts/ast_grep_helper.py scan -U src/
# CI-friendly GitHub annotations
python scripts/ast_grep_helper.py scan --report-style short src/validate — 通信せずにパターンを検査する(sg を呼びません)
CI での検査、コミット前のフック、ちょっとした確認に向いています。
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 # list 25 supported languages and aliases
python scripts/ast_grep_helper.py doctor # check ast-grep binary availability
python scripts/ast_grep_helper.py install # delegate to install.sh / install.ps1new と test は、そのまま sg new・sg test に渡されます。
sg を直接使う(ラッパーでは足りないとき)
ラッパーはやり方を決め打ちにしています。細かく制御したいときは sg を直接使ってください。この skill には references/cli.md に CLI の早見表が入っています。最小限の使い方は次のとおりです。
# Search
sg run -p 'console.log($MSG)' --lang ts src/
# Search with JSON for scripting
sg run -p 'console.log($MSG)' --lang ts --json=compact src/
# Rewrite, dry-run
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --lang ts --json=compact src/
# Rewrite, apply
sg run -p 'console.log($MSG)' -r 'logger.info($MSG)' --lang ts --update-all src/
# Pattern from stdin (great for ad-hoc experiments)
echo 'console.log("hi")' | sg run -p 'console.log($MSG)' --lang js --stdin
# Debug a pattern that returns 0 matches
sg run -p '<your pattern>' --lang <lang> --debug-query=ast --stdin <<< '<sample-code>'
# Run YAML rules
sg scan src/
# Inline YAML rule (one-off)
sg scan --inline-rules '
id: no-todo
language: TypeScript
severity: warning
rule: { pattern: TODO }' src/シェルから sg を直接使うときは、パターンを必ずシングルクォートで囲んで ください。そうしないと $VAR がシェルに展開されます。
何をいつ使うかの見取り図
<!-- ascii-guard-ignore -->
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<!-- ascii-guard-ignore-end -->
「すべて探して」と頼まれたら、対象が形のあるもの(関数、クラス、呼び出し、import、文)なら ast-grep を既定にします。対象が文字の並び(文字列の中身、コメント、ライセンス表記、ファイル名、識別子の一部)なら search_files を既定にします。
書き換えるときは、必ず先に下見をする
パターンを間違えると、何も言わないまま別のものが書き換わります。ラッパーの replace が既定で下見だけにしてあるのは、このためです。手順は次のとおりです。
- まず検索して、一致するものを確かめます:
helper search '<pattern>' --lang X . - 書き換えを下見します:
helper replace '<pattern>' '<rewrite>' --lang X .(--applyは付けません) - 下見の結果を確かめます。一致した数、影響するファイル、場所ごとの書き換え後の姿です。
- 違っていたら、パターンを直して 1 に戻ります。
- 合っていたら
helper replace '<pattern>' '<rewrite>' --lang X . --applyを実行します。
下見をしていない書き換えを当てないでください。git のリポジトリで --apply したあとは、コミットの前に git diff --stat で見直します。
コードはあるはずなのに sg が 0 件を返すとき
上から順に試してください。
helper validate '<pattern>' --lang <lang>を実行します — 正規表現の混入、関数の本体の書き忘れ、Python の末尾のコロンを見つけてくれます。--langを確かめます —sgは拡張子から推測します。.tsxのファイルに--lang ts(tsxではありません)を渡すと、JSX が解析できません。- 解析されたパターンを見ます:
sg run -p '<pattern>' --lang <lang> --debug-query=ast --stdin <<< '<sample>'。ERRORのノードが出ていれば、パターンが壊れています。 - 対象ファイルの構文木を見ます:
sg run -p '$_' --lang <lang> --debug-query=cst path/to/file | head -40を実行し、一致させたいkindを探します。 - playground を試します: <https://ast-grep.github.io/playground.html> にコードとパターンを貼ると、何が起きているか見えます。
当てずっぽうで書き換えて再試行しないでください。外れには必ず理由があるので、それを突き止めます。
YAML の決まりと、その場で書く -p パターンの使い分け
その場で -p を書く のは、こんなときです。
- 一度きりの調べもの。
- パターンが単純(条件も、修正の型もない)。
- まだ探っている段階。
YAML の決まりを使う(rules/ の下にファイルを置き、sg scan で走らせる)のは、こんなときです。
- 同じパターンを繰り返し使う(検査の決まり、CI で走らせる一括変換)。
constraints、transform、入り組んだinside/has、複数条件の組み合わせが要る。- 自動修正(
fix:の項目)を使いたい。 - 決まり自体を試験したい(
sg testによる記録との突き合わせ)。
YAML の決まりの書き方は 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が端末で読みやすい色付きの出力を出します。- ラッパーの既定は読みやすい形(file:line:column と一致部分の抜粋)です。そのままの JSON がほしいときは
--json-outを付けます。 - ラッパーの
replaceは必ずまとめを出します。一致した数、ファイルの数、場所ごとの書き換え後の姿です。
利用者に結果を伝えるときは、一致した数だけでなく、影響するファイルの数も必ず入れて ください。どこまで波及するかが知りたいからです。
先に読むもの(優先度の順)
references/patterns.md— メタ変数、名前の付け方、厳密さの段階。パターンが一致しない理由がわからないときに読みます。references/pitfalls.md— 外れ方をまとめた手引き。0 件が意外だったときに読みます。references/recipes.md— 言語ごとの、そのまま使えるパターン集。新しい作業を始めるときに最初に読みます。references/cli.md—sg run、sg scan、sg test、sg new、sg lsp。ラッパーでは足りないときに読みます。references/yaml-rules.md— YAML の決まりの書き方。その場で書くパターンでは手に負えなくなったら読みます。references/sgconfig.md— プロジェクト全体の設定。実際のプロジェクトでsg scanを用意するときに読みます。references/install.md— OS ごとの導入方法。install.sh/install.ps1がうまくいかないときだけ読みます。
守ること
- 検索の前に検査する。 プログラムでパターンを組み立てるときは、まず
helper validateを呼びます。「0 件」の原因を調べる作業のおよそ 7 割を占める、正規表現の混入を捕まえてくれます。 - 当てる前に下見する。 一致したものを確かめずに
sg run -r ... --update-allを実行しないでください。ラッパーのreplaceは、既定でこれを守らせます。 - 書き換えは 2 回に分ける。
sgを直接使って下見と適用の両方をするときは、2 回呼びます。--jsonを付けると--update-allが無視されます。 - シェルではパターンをシングルクォートで囲む。
"$VAR"ではなく'$VAR'です。ダブルクォートだとシェルが$VARを空文字に展開し、パターンが壊れます。 - パターンはコードであって、正規表現ではない。
|、.*、\w、[a-z]が要る形になったら、search_files に切り替えてください。ast-grep を正規表現のように使おうとしないことです。 - 標準入力から読むときは
--langが要る。--stdinでパイプするときは--langを明示してください。拡張子がないのでsgには推測できません。 - Linux では
sgよりast-grepを使う。sgはutil-linuxのsetgroupsとぶつかります。ラッパーはこれを見て使い分けますが、sgを直接呼ぶならalias sg=ast-grepと別名を付けてください。