
Hai Ast Grep
- 6 installs
- 277 repo stars
- Updated June 11, 2026
- hylarucoder/hai-stack
Produces a validated ast-grep pattern or YAML rule for structural code search, lint, or auto-rewrite using tree-sitter AST matching.
About
Ships ast-grep patterns and rules that structurally search, lint, or rewrite code via tree-sitter, validated against positive and negative fixtures. A developer uses it for repo-wide codemods, call-site searches, or CI lint guards where grep over- or under-matches.
- CLI run for one-off search/rewrite, YAML rules for reusable lint/codemod
- Every pattern validated against a positive and negative case
Hai Ast Grep by the numbers
- 6 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,691 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hylarucoder/hai-stack --skill hai-ast-grepAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 277 |
| Last updated | June 11, 2026 |
| Repository | hylarucoder/hai-stack ↗ |
What it does
Produces a validated ast-grep pattern or YAML rule for structural code search, lint, or auto-rewrite using tree-sitter AST matching.
Files
hai-ast-grep
ast-grep uses tree-sitter to parse code into AST, enabling precise pattern matching. Reach for it whenever a search or refactor depends on syntax structure: one-off searches and rewrites run straight from the CLI, reusable lint/codemod rules are written in YAML. Either way the job is not "write a pattern" — it is to ship a pattern or rule validated against a positive AND a negative case, so it catches what it should and nothing it shouldn't.
Project Configuration
Project-level batch scanning via ast-grep scan requires an sgconfig.yml config file; one-off pattern tests via ast-grep run -p '<pattern>' do not.
# sgconfig.yml (project root)
ruleDirs:
- rules # rule directory; recursively loads all .yml filesTypical project structure:
my-project/
├── sgconfig.yml
├── rules/
│ ├── no-console.yml
│ └── custom/
│ └── team-rules.yml
└── src/Run a project scan:
ast-grep scan # auto-discovers sgconfig.yml
ast-grep scan --config path/to/sgconfig.yml # explicit configNote: theast-grep scancommand requiressgconfig.yml, whileast-grep run -pworks standalone.
Everyday CLI Usage (no rule file)
Most day-to-day search and refactor work never needs YAML or sgconfig — ast-grep run (the default subcommand) does it directly:
# Search: every fetch call site, regardless of formatting
ast-grep run -p 'fetch($URL)' -l ts src/
# Search with context lines, or machine-readable output for a report
ast-grep run -p 'console.log($$$)' -C 2 src/
ast-grep run -p 'console.log($$$)' --json=stream src/
# One-off rewrite: review each match interactively (-i), or apply all (-U)
ast-grep run -p 'console.log($$$A)' -r 'logger.log($$$A)' -l ts -i src/
ast-grep run -p 'oldFn($A, $B)' -r 'newFn($B, $A)' -l ts -U src/Flags that matter:
| Flag | Meaning |
|---|---|
-p <pattern> | the AST pattern to match |
-r <template> | rewrite template — in run; in scan, -r means rule FILE, don't mix them up |
-l <lang> | language (ts, tsx, py, go, rs…); inferred from file extensions when omitted |
-i | interactive accept/reject per match — default this before any mass rewrite |
-U | apply all rewrites; without it matches are only reported |
-C <n> / --json | context lines / JSON output |
Deliver the pattern, the exact command, and a match summary. Escalate to a YAML rule only when the match needs constraints / not / inside narrowing, or will be re-run (CI guard, reusable codemod).
Rule Workflow
Lint Rule (most common)
Check-only, no fix — for CI / editor diagnostics:
# rules/no-console-log.yml
id: no-console-log
language: JavaScript
severity: warning
message: Avoid console.log in production code
rule:
pattern: console.log($$$ARGS)Validate:
ast-grep scan -r rules/no-console-log.yml src/Rewrite Rule (optional)
To auto-fix, add ONE fix: line to the lint rule above — nothing else changes:
# ... same rule as above, plus:
fix: logger.log($$$ARGS)Apply the fix (note the --update-all flag — scan without it only reports):
ast-grep scan -r rules/no-console-log.yml --update-all src/Development Flow (canonical workflow — follow these steps)
1. Explore the pattern via CLI before writing YAML: ast-grep -p 'console.log($ARG)' src/. Inspect node types with --debug-query ast when the pattern won't match:
ast-grep -p 'console.log($ARG)' --debug-query ast2. Write the rule file (.yml) — start with the lint form (pattern + message + severity). 3. Validate against a POSITIVE fixture — code that should match: ast-grep scan -r rule.yml fixtures/. Confirm it matches. 4. Validate against a NEGATIVE fixture — code that looks similar but should NOT match. If it matches, you have a false positive: add constraints, not, inside, or has to narrow the rule, then re-run both fixtures. 5. Add `fix:` only if a mechanical rewrite is wanted, then dry-run before --update-all. 6. Deliver using the deliverable shape below (and references/output-template.md) — never hand back a bare YAML block.
Essential Syntax
Cheat sheet for fast in-context lookup. Full syntax in references/rule-syntax.md.
| Element | Syntax | Example |
|---|---|---|
| Single node | $VAR | console.log($MSG) |
| Multiple nodes | $$$ARGS | fn($$$ARGS) |
| Same content | Use same name | $A == $A |
| Non-capturing | $_VAR | $_FN($_FN) |
| Capture unnamed | $$VAR | async function $$NAME() {} |
Core Rules Quick Reference
Cheat sheet. Full atomic / composite / relational rules in references/rule-syntax.md.
| Type | Purpose | Example |
|---|---|---|
pattern | Match code structure | pattern: if ($COND) {} |
kind | Match AST node type | kind: function_declaration |
all | Match ALL conditions | all: [pattern: X, kind: Y] |
any | Match ANY condition | any: [pattern: var $A, pattern: let $A] |
not | Exclude matches | not: {pattern: safe_call()} |
has | Must have child | has: {kind: return_statement} |
inside | Must be in ancestor | inside: {kind: class_body} |
Deliverable Shape
Hand back the rule in this shape — not a bare YAML block. These are the five headers from references/output-template.md; read that file for the full template before finalizing.
- Goal — what code pattern this finds or rewrites.
- Rule — the
.yml(id, language, rule, message, severity). - Fix, if applicable — the added
fix:line. - Validation — positive fixture (should match), negative fixture (should NOT match), the exact command run (
ast-grep scan --rule <file>or-r <file> src/), and the result. - Notes — false positives avoided (how), and known limits (cases intentionally not covered).
Detailed References
Complete syntax guide: See references/rule-syntax.md
- Atomic rules (pattern, kind, regex, nthChild, range)
- Composite rules (all, any, not, matches)
- Relational rules (has, inside, follows, precedes)
- Transform and fixConfig
Language-specific patterns: See references/common-patterns.md
- JavaScript/TypeScript examples
- Python examples
- Go and Rust examples
Output template: See references/output-template.md — the full, copy-pasteable version of the Deliverable Shape above (positive/negative fixtures, exact validation command, known-limits field).
Supported Languages
Bash, C, Cpp, CSharp, Css, Elixir, Go, Haskell, Hcl, Html, Java, JavaScript, Json, Kotlin, Lua, Nix, Php, Python, Ruby, Rust, Scala, Solidity, Swift, Tsx, TypeScript, Yaml
Use a Different Skill When
ast-grep is the right hammer only when the match depends on syntax structure — search, lint, or rewrite. Route elsewhere when:
- Plain text or regex find-and-replace with no syntax-tree shape (rename a string literal, swap a URL, find a unique identifier that grep already nails) — just use grep / a normal edit /
sed; an AST pattern is overkill. - One-off edit in a single file — edit it directly; a rule only pays off across many call sites.
- Type-aware or semantic refactor (driven by what a value's type is, not its syntax — e.g. eliminate
any) — usets-type-safety-reviewer. ast-grep matches syntax, not types. - Subjective code-quality review ("is this clean / well-named / over-engineered", code smells) — use
clean-code-reviewer; for a behavior-preserving cleanup pass usecode-simplifier. - Pure formatting / whitespace / import order — that is a formatter's job (Prettier, Biome, gofmt), not a structural rule.
Common ast-grep Patterns
Contents
---
JavaScript/TypeScript
Find function calls
rule:
pattern:
context: $M($$$);
selector: call_expressionFind class methods
rule:
kind: method_definition
inside:
kind: class_bodyReplace console.log with logger
Input:
console.log("debug message");
console.log(data, options);Rule:
id: replace-console-log
language: JavaScript
rule:
pattern: console.log($$$ARGS)
fix: logger.log($$$ARGS)Output:
logger.log("debug message");
logger.log(data, options);Migrate var to const/let
id: no-var
language: JavaScript
rule:
pattern: var $A = $B
fix: const $A = $B
message: Use const instead of varFind React useEffect missing deps
id: useeffect-missing-deps
language: TypeScript
rule:
pattern: useEffect($FN, [])
has:
pattern: $VAR
inside:
kind: arrow_function
not:
inside:
kind: array---
Python
Replace print with logging
Input:
print("Starting process")
print(f"Value: {value}")Rule:
id: replace-print
language: Python
rule:
pattern: print($$$ARGS)
fix: logging.info($$$ARGS)Output:
logging.info("Starting process")
logging.info(f"Value: {value}")Find bare except
id: no-bare-except
language: Python
rule:
kind: except_clause
not:
has:
kind: identifier
message: Avoid bare except, specify exception type---
Go
Find error ignoring
id: check-error
language: Go
rule:
pattern: $_, _ = $FUNC($$$)
message: Don't ignore errorsReplace fmt.Println with log
id: use-log
language: Go
rule:
pattern: fmt.Println($$$ARGS)
fix: log.Println($$$ARGS)---
Rust
Find unwrap usage
id: no-unwrap
language: Rust
rule:
pattern: $EXPR.unwrap()
message: Consider using ? or expect() instead of unwrap()
severity: warningReplace println! with tracing
id: use-tracing
language: Rust
rule:
pattern: println!($$$ARGS)
fix: tracing::info!($$$ARGS)ast-grep Rule Output Template
Use this template when delivering an ast-grep search or rewrite rule. Include enough validation detail that the rule can be trusted and rerun.
````markdown
ast-grep Rule: <rule name>
Goal
<What code pattern this rule should find or rewrite.>
Rule
id: <rule-id>
language: <language>
rule:
pattern: <pattern>
message: <reader-facing message>
severity: warningFix, If Applicable
fix: <replacement>Validation
- Positive fixture: <code that should match>
- Negative fixture: <code that should not match>
- Command run:
ast-grep scan --rule <rule-file> - Result: <matches found / fixture behavior>
Notes
- False positives avoided: <how>
- Known limits: <cases intentionally not covered>
````
ast-grep Rule Syntax Reference
Contents
---
Atomic Rules
pattern
Matches syntax node using pattern syntax.
rule:
pattern: console.log($GREETING)Pattern object for ambiguous code:
rule:
pattern:
context: 'class A { $FIELD = $INIT }'
selector: field_definitionkind
Matches AST node type by tree-sitter name.
rule:
kind: field_definitionESQuery-style selector (v0.39.1+):
rule:
kind: call_expression > identifierregex
Matches node text with Rust regex. Always combine with other rules for performance:
rule:
kind: identifier
regex: "^debug"nthChild
Matches nodes by position (1-based, CSS-style).
rule:
kind: number
nthChild: 2Advanced:
rule:
nthChild:
position: 2n+1
reverse: true
ofRule:
kind: function_declarationrange
Matches by source code position.
rule:
range:
start: {line: 0, column: 0}
end: {line: 1, column: 5}---
Composite Rules
all
Match nodes satisfying ALL rules.
rule:
all:
- pattern: console.log('Hello World')
- kind: expression_statementany
Match nodes satisfying ANY rule.
rule:
any:
- pattern: var $A = $B
- pattern: const $A = $B
- pattern: let $A = $Bnot
Negate a rule.
rule:
pattern: console.log($GREETING)
not:
pattern: console.log('Hello World')matches
Reference a utility rule.
rule:
matches: utility-rule-name---
Relational Rules
has
Node must have matching child.
rule:
kind: function_declaration
has:
kind: return_statementinside
Node must be inside matching ancestor.
rule:
pattern: this.$PROP
inside:
kind: class_bodyfollows
Node must follow matching sibling.
rule:
pattern: $VAL
follows:
kind: property_identifierprecedes
Node must precede matching sibling.
rule:
pattern: return $VAL
precedes:
pattern: $A = $VALdirect
Limit to immediate children.
rule:
kind: class_body
has:
direct: true
kind: field_definitionstopBy
Stop searching at matching node.
rule:
pattern: $EXP
inside:
kind: function_body
stopBy:
kind: return_statement---
Pattern Syntax
| Syntax | Description | Example |
|---|---|---|
$NAME | Match exactly one AST node | console.log($GREETING) |
$$$ARGS | Match zero or more nodes | console.log($$$ARGS) |
$_NAME | Non-capturing (independent match) | $_FUNC($_FUNC) |
$$VAR | Capture unnamed nodes | async function $$NAME() {} |
Variable Capturing: Same name = same content.
pattern: $A == $A # matches: a == a, 1 == 1; not: a == b---
Constraints
Filter meta-variables after pattern match.
rule:
pattern: console.log($ARG)
constraints:
ARG:
kind: number---
Fix & Transform
Simple Fix
rule:
pattern: console.log($$$ARGS)
fix: logger.log($$$ARGS)Transform
transform:
NEW_VAR:
replace:
source: $OLD
replace: debug(?<TAIL>.*)
by: release$TAIL
fix: $NEW_VARString style (v0.38.3+):
transform:
LIST: substring($GEN, startChar=1, endChar=-1)
KEBABED: convert($OLD_FN, toCase=kebabCase)FixConfig
For list items with commas.
fix:
template: ''
expandEnd: {regex: ','}---
Utility Rules
Define reusable rules locally:
utils:
match-function:
any:
- kind: function_declaration
- kind: arrow_function
rule:
matches: match-functionhai-ast-grep 中文版
本文件是中文阅读版;执行规则以 SKILL.md 为准。
ast-grep 用 tree-sitter 把代码解析成 AST,从而做到精确的模式匹配。凡是搜索或重构依赖语法结构的场景都该用它:一次性的搜索和改写直接走 CLI,可复用的 lint/codemod 规则用 YAML 编写。无论哪种形态,任务都不是"写一个 pattern"——而是交付一个经过正例和反例双重验证的 pattern 或规则:该匹配的都匹配,不该匹配的一个不碰。
项目配置
通过 ast-grep scan 做项目级批量扫描需要 sgconfig.yml 配置文件;通过 ast-grep run -p '<pattern>' 做一次性 pattern 测试则不需要。
# sgconfig.yml(项目根目录)
ruleDirs:
- rules # 规则目录;递归加载所有 .yml 文件典型项目结构:
my-project/
├── sgconfig.yml
├── rules/
│ ├── no-console.yml
│ └── custom/
│ └── team-rules.yml
└── src/运行项目扫描:
ast-grep scan # 自动发现 sgconfig.yml
ast-grep scan --config path/to/sgconfig.yml # 显式指定配置注意:ast-grep scan命令需要sgconfig.yml,而ast-grep run -p可以独立运行。
日常 CLI 用法(不写规则文件)
日常的搜索和重构大多数根本不需要 YAML 和 sgconfig——ast-grep run(默认子命令)直接搞定:
# 搜索:fetch 的全部调用点,无视换行和格式差异
ast-grep run -p 'fetch($URL)' -l ts src/
# 带上下文行搜索,或输出机器可读的 JSON 用于报告
ast-grep run -p 'console.log($$$)' -C 2 src/
ast-grep run -p 'console.log($$$)' --json=stream src/
# 一次性改写:逐个交互确认(-i),或全量应用(-U)
ast-grep run -p 'console.log($$$A)' -r 'logger.log($$$A)' -l ts -i src/
ast-grep run -p 'oldFn($A, $B)' -r 'newFn($B, $A)' -l ts -U src/关键参数:
| 参数 | 含义 |
|---|---|
-p <pattern> | 要匹配的 AST pattern |
-r <template> | 改写模板——这是 run 里的含义;在 scan 里 -r 指规则文件,不要混淆 |
-l <lang> | 语言(ts、tsx、py、go、rs……);省略时按文件扩展名推断 |
-i | 逐个匹配交互式接受/拒绝——任何批量改写前默认先用它 |
-U | 应用全部改写;不带它时只报告匹配,不动文件 |
-C <n> / --json | 上下文行数 / JSON 输出 |
交付 pattern、所用的完整命令和匹配摘要。只有当匹配需要 constraints / not / inside 收窄,或者会被反复运行(CI 守护、可复用 codemod)时,才升级成 YAML 规则。
规则工作流
Lint 规则(最常见)
只检查、不修复——用于 CI / 编辑器诊断:
# rules/no-console-log.yml
id: no-console-log
language: JavaScript
severity: warning
message: Avoid console.log in production code
rule:
pattern: console.log($$$ARGS)验证:
ast-grep scan -r rules/no-console-log.yml src/改写规则(可选)
要自动修复,在上面的 lint 规则里加一行 fix:——其余不变:
# ……规则同上,外加:
fix: logger.log($$$ARGS)应用修复(注意 --update-all 参数——不带它的 scan 只报告不改写):
ast-grep scan -r rules/no-console-log.yml --update-all src/开发流程(规范工作流——按步骤执行)
1. 先用 CLI 探索 pattern,再写 YAML:ast-grep -p 'console.log($ARG)' src/。pattern 匹配不上时用 --debug-query ast 查看节点类型:
ast-grep -p 'console.log($ARG)' --debug-query ast2. 编写规则文件(.yml)——从 lint 形态起步(pattern + message + severity)。 3. 用正例 fixture 验证——应该匹配的代码:ast-grep scan -r rule.yml fixtures/,确认匹配。 4. 用反例 fixture 验证——长得像但不该匹配的代码。如果匹配了就是误报:加 constraints、not、inside 或 has 收窄规则,然后两个 fixture 重新跑一遍。 5. 只在需要机械化改写时加 `fix:`,并在 --update-all 之前先 dry-run。 6. 按下方交付形态交付(以及 references/output-template.md)——绝不只甩回一段裸 YAML。
基础语法
供上下文内快速查阅的速查表。完整语法见 references/rule-syntax.md。
| 元素 | 语法 | 示例 |
|---|---|---|
| 单个节点 | $VAR | console.log($MSG) |
| 多个节点 | $$$ARGS | fn($$$ARGS) |
| 内容相同 | 使用同名变量 | $A == $A |
| 不捕获 | $_VAR | $_FN($_FN) |
| 捕获匿名节点 | $$VAR | async function $$NAME() {} |
核心规则速查
速查表。完整的原子 / 组合 / 关系规则见 references/rule-syntax.md。
| 类型 | 用途 | 示例 |
|---|---|---|
pattern | 匹配代码结构 | pattern: if ($COND) {} |
kind | 匹配 AST 节点类型 | kind: function_declaration |
all | 满足全部条件 | all: [pattern: X, kind: Y] |
any | 满足任一条件 | any: [pattern: var $A, pattern: let $A] |
not | 排除匹配 | not: {pattern: safe_call()} |
has | 必须包含某子节点 | has: {kind: return_statement} |
inside | 必须位于某祖先内 | inside: {kind: class_body} |
交付形态
按这个形态交付规则——不是一段裸 YAML。以下是 references/output-template.md 的五个标题;定稿前读那个文件拿完整模板。
- 目标——这条规则找到或改写什么代码模式。
- 规则——
.yml文件(id、language、rule、message、severity)。 - 修复(如适用)——新增的
fix:行。 - 验证——正例 fixture(应匹配)、反例 fixture(不应匹配)、实际运行的完整命令(
ast-grep scan --rule <file>或-r <file> src/)及结果。 - 备注——规避了哪些误报(如何规避),以及已知边界(有意不覆盖的情况)。
详细参考
完整语法指南:见 references/rule-syntax.md
- 原子规则(pattern、kind、regex、nthChild、range)
- 组合规则(all、any、not、matches)
- 关系规则(has、inside、follows、precedes)
- Transform 与 fixConfig
特定语言的常用模式:见 references/common-patterns.md
- JavaScript/TypeScript 示例
- Python 示例
- Go 和 Rust 示例
输出模板:见 references/output-template.md——上方交付形态的完整可复制版本(正反例 fixture、完整验证命令、已知边界字段)。
支持的语言
Bash, C, Cpp, CSharp, Css, Elixir, Go, Haskell, Hcl, Html, Java, JavaScript, Json, Kotlin, Lua, Nix, Php, Python, Ruby, Rust, Scala, Solidity, Swift, Tsx, TypeScript, Yaml
何时改用其他 skill
只有当匹配依赖语法结构时——搜索、lint 或改写——ast-grep 才是对的锤子。以下情况换路:
- 纯文本或正则查找替换,不涉及语法树形状(改一个字符串字面量、换一个 URL、找一个 grep 一发就命中的唯一标识符)——直接 grep / 普通编辑 /
sed;AST pattern 是杀鸡用牛刀。 - 单文件一次性修改——直接改;规则只有跨大量调用点才回本。
- 类型感知或语义重构(由值的类型而非语法驱动——例如消除
any)——用ts-type-safety-reviewer。ast-grep 匹配的是语法,不是类型。 - 主观代码质量评审("这代码干不干净 / 命名好不好 / 是否过度设计"、坏味道)——用
clean-code-reviewer;保持行为不变的清理用code-simplifier。 - 纯格式化 / 空白 / import 顺序——那是格式化器的活(Prettier、Biome、gofmt),不是结构化规则的活。