Agent skill
ast-grep
Structural code search and refactoring using AST patterns. Use when searching for code patterns (not text), finding deprecated patterns, or systematic refactoring. Supports Nextflow, JavaScript, Python, and more.
Install this agent skill to your Project
npx add-skill https://github.com/edmundmiller/dotfiles/tree/main/config/agents/skills/ast-grep
Metadata
Additional technical details for this skill
- author
- Edmund Miller
- version
- 1.0.0
SKILL.md
ast-grep Skill
ast-grep is a structural code search and rewriting tool. Use it when you need to find or modify code based on its AST structure rather than text patterns.
When to Use ast-grep
Use ast-grep when:
- Searching for code patterns (function calls, imports, specific constructs)
- Refactoring code systematically
- Finding deprecated patterns or anti-patterns
- The pattern involves code structure, not just text
Use grep/ripgrep when:
- Searching for literal strings, comments, or documentation
- Simple text matching is sufficient
Quick Reference
| Task | Command |
|---|---|
| Find pattern | ast-grep run --pattern 'PATTERN' --lang LANG |
| Scan with rule | ast-grep scan --rule file.yaml |
| Debug AST | --debug-query=ast |
| Test pattern match | --debug-query=pattern |
| Use inline rule | --inline-rules 'YAML' |
| Nextflow patterns | Use _VAR instead of $VAR |
Quick Start
Simple Pattern Search
# Find all console.log calls
ast-grep run --pattern 'console.log($$$ARGS)' --lang js
# Find Channel.from() calls in Nextflow
ast-grep run --pattern 'Channel.from(___)' --lang nextflow
Using Rules for Complex Patterns
# Scan with a rule file
ast-grep scan --rule path/to/rule.yaml
# Quick inline rule test
ast-grep scan --inline-rules '
id: test-rule
language: javascript
rule:
pattern: console.log($$$ARGS)
'
Debugging Patterns
# See AST structure of code
ast-grep run --pattern '$$$' --debug-query=ast path/to/file.js
# See how pattern matches
ast-grep run --pattern 'your_pattern' --debug-query=pattern path/to/file.js
Core Concepts
Metavariables
| Syntax | Matches | Example |
|---|---|---|
$VAR |
Single named node | console.$METHOD matches console.log |
$$VAR |
Single node (including anonymous) | $$OP matches operators |
$$$VAR |
Zero or more nodes | func($$$ARGS) matches any args |
_ prefix |
Non-capturing (Nextflow) | _VAR instead of $VAR |
Note: In Nextflow, use _ instead of $ for metavariables (configured via expandoChar in sgconfig.yml).
Rule Structure
id: rule-name
language: javascript # or nextflow, python, etc.
severity: warning # error, warning, hint, off
message: "Human-readable message"
note: |
Additional context and fix suggestions
rule:
pattern: code_pattern_here
Workflow for Writing Rules
- Understand the query - What code pattern are you looking for?
- Create example code - Write a small file with the pattern
- Inspect the AST - Use
--debug-query=astto see structure - Write initial pattern - Start simple, use metavariables
- Test and refine - Use
--debug-query=patternto debug matches - Add constraints - Use relational rules (
inside,has) as needed
Common Patterns
Match function with specific content
rule:
all:
- pattern: function $NAME($$$PARAMS) { $$$ }
- has:
pattern: console.log($$$)
stopBy: end
Match code inside a context
rule:
all:
- pattern: await $PROMISE
- inside:
kind: try_statement
stopBy: end
Match missing pattern (lint for absence)
rule:
all:
- pattern: function $NAME($$$) { $$$ }
- not:
has:
pattern: return $$$
stopBy: end
Key Principles
- Always use
stopBy: endfor relational rules (inside,has,precedes,follows) to search the full subtree - Start simple - Get a basic pattern working before adding complexity
- Escape
$in shell - Use\$VARor single quotes when running from bash - Use
allfor order - When metavariables depend on each other,allprocesses rules in order
Integration Notes
OpenCode Tools
This repository includes nf-ast-grep MCP tools for Nextflow-specific searches:
nf-ast-grep_find_processes- Find all process definitionsnf-ast-grep_find_workflows- Find workflow definitionsnf-ast-grep_find_channels- Find channel factory operationsnf-ast-grep_find_deprecated- Find deprecated patternsnf-ast-grep_lint- Run lint rules on Nextflow codenf-ast-grep_search- Search with custom patterns
Use these tools for Nextflow work instead of raw ast-grep commands when available.
Shell Execution
When running ast-grep from bash:
- Single quotes preserve
$metavariables:ast-grep run --pattern 'console.$METHOD' - Escape in double quotes:
ast-grep run --pattern "console.\$METHOD" - For complex patterns, use
--inline-ruleswith a heredoc or rule files
When to Delegate
- Large searches: Use explore agents for searching across large codebases
- Simple patterns: Run ast-grep directly for quick one-off searches
- Rule development: Use iterative bash commands with
--debug-query
Error Handling
"Pattern not matching expected code"
Cause: Pattern syntax doesn't match AST structure.
Solution:
- Inspect actual AST:
ast-grep run --pattern '$$$' --debug-query=ast file.ext - Check node kinds match what you expect
- Simplify pattern and add constraints incrementally
"ast-grep: command not found"
Cause: ast-grep not installed or not in PATH.
Solution:
# Install via cargo
cargo install ast-grep
# Or via npm
npm install -g @ast-grep/cli
# Or via nix
nix shell nixpkgs#ast-grep
"Language not supported"
Cause: Trying to use an unsupported or unconfigured language.
Solution:
- Check supported languages:
ast-grep --help - For custom languages (like Nextflow), ensure
sgconfig.ymlis present withcustomLanguagesconfigured - See Nextflow Reference for custom language setup
"Missing stopBy in relational rule"
Cause: Relational rules (inside, has, precedes, follows) default to stopBy: neighbor which only checks immediate children.
Solution: Always add stopBy: end to search the full subtree:
rule:
has:
pattern: target_pattern
stopBy: end # Don't forget this!
"Metavariable not captured"
Cause: Using _ prefix makes metavariables non-capturing, or metavariable used before it's defined.
Solution:
- Use
$VAR(or_VARin Nextflow) for capturing - In
allblocks, define metavariables before using them (rules process in order)
Shell escaping issues
Cause: $ in patterns gets interpreted as shell variable.
Solution:
# Use single quotes (preferred)
ast-grep run --pattern 'console.$METHOD'
# Or escape in double quotes
ast-grep run --pattern "console.\$METHOD"
# Or use rule files to avoid shell entirely
ast-grep scan --rule my-rule.yaml
"Rule file not found" or YAML errors
Cause: Invalid YAML syntax or wrong file path.
Solution:
- Validate YAML syntax (check indentation, colons, quotes)
- Use absolute paths or run from correct directory
- Test with
--inline-rulesfirst before creating rule file
Examples
Example 1: Finding and Fixing Deprecated Nextflow Patterns
User request: "Find all uses of the deprecated Channel.from() in our Nextflow pipeline"
Workflow:
# Step 1: Quick search to see scope of problem
ast-grep run --pattern 'Channel.from(___)' --lang nextflow
# Step 2: If many results, use the lint tool for structured output
# (uses existing deprecated-channel-from.yaml rule)
Rule used (deprecated-channel-from.yaml):
id: deprecated-channel-from
language: nextflow
severity: warning
message: "Channel.from() is deprecated in DSL2"
note: |
Use Channel.of() for simple values or Channel.fromList() for lists.
Before: Channel.from(1, 2, 3)
After: Channel.of(1, 2, 3)
rule:
pattern: Channel.from(___)
Result: Found 3 instances in main.nf, updated to use Channel.of().
Example 2: Refactoring Console Logging Across a Codebase
User request: "Replace all console.log calls with our custom logger"
Workflow:
# Step 1: Find all console.log calls
ast-grep run --pattern 'console.log($$$ARGS)' --lang js
# Step 2: Create a rewrite rule
cat > /tmp/replace-console.yaml << 'EOF'
id: replace-console-log
language: javascript
rule:
pattern: console.log($$$ARGS)
fix: logger.info($$$ARGS)
EOF
# Step 3: Preview changes
ast-grep scan --rule /tmp/replace-console.yaml
# Step 4: Apply changes (with --update-all)
ast-grep scan --rule /tmp/replace-console.yaml --update-all
Key insight: The fix field in rules enables automatic refactoring, not just finding.
Example 3: Creating a Custom Linting Rule from Scratch
User request: "Create a rule that warns when async functions don't have error handling"
Workflow:
# Step 1: Create example code to understand the AST
cat > /tmp/example.js << 'EOF'
async function good() {
try {
await fetch('/api');
} catch (e) {
console.error(e);
}
}
async function bad() {
await fetch('/api'); // No try-catch!
}
EOF
# Step 2: Inspect AST structure
ast-grep run --pattern '$$$' --debug-query=ast /tmp/example.js
# Step 3: Write initial pattern for async functions with await
ast-grep run --pattern 'async function $NAME($$$) { $$$ }' /tmp/example.js
# Step 4: Add constraint: must have await but no try statement
cat > /tmp/async-error-handling.yaml << 'EOF'
id: async-needs-error-handling
language: javascript
severity: warning
message: "Async function '$NAME' has await but no try-catch"
note: |
Async functions with await should have error handling.
Wrap await calls in try-catch blocks.
rule:
all:
- pattern: async function $NAME($$$PARAMS) { $$$BODY }
- has:
pattern: await $$$
stopBy: end
- not:
has:
kind: try_statement
stopBy: end
EOF
# Step 5: Test the rule
ast-grep scan --rule /tmp/async-error-handling.yaml /tmp/example.js
Result: Rule correctly flags bad() but not good().
Example 4: Finding Implicit Closure Parameters in Nextflow
User request: "Find closures that use implicit 'it' parameter - we want explicit parameters for readability"
Workflow:
# Use the existing rule via nf-ast-grep tools
# Or run directly:
ast-grep scan --rule config/opencode/skills/ast-grep/rules/implicit-it-closure.yaml
Rule explanation (implicit-it-closure.yaml):
id: implicit-it-closure
language: nextflow
severity: hint
message: "Consider using explicit closure parameter instead of implicit 'it'"
rule:
all:
- kind: closure
- has:
pattern: it
stopBy: end
- not:
has:
kind: closure_parameter
stopBy: end
This uses kind matching (AST node type) combined with has/not has to find closures that reference it but don't declare a parameter.
Example 5: Multi-Pattern Search with Dependencies
User request: "Find all React components that use useState but don't have a useEffect cleanup"
Workflow:
cat > /tmp/missing-cleanup.yaml << 'EOF'
id: missing-useeffect-cleanup
language: typescript
severity: warning
message: "Component uses useState but may be missing useEffect cleanup"
rule:
all:
- kind: function_declaration
- has:
pattern: useState($$$)
stopBy: end
- has:
pattern: useEffect($$$)
stopBy: end
- not:
has:
pattern: |
useEffect(() => {
$$$SETUP
return $$$CLEANUP
}, $$$DEPS)
stopBy: end
EOF
ast-grep scan --rule /tmp/missing-cleanup.yaml src/
Key insight: Complex patterns can combine multiple has and not has clauses to express sophisticated constraints.
References
For detailed syntax and advanced features, see:
- Rule Reference - Complete rule syntax documentation
- Nextflow Reference - Nextflow-specific patterns and examples
Progressive Disclosure
This skill document provides a practical introduction to ast-grep. For deeper understanding:
- Start here - Quick reference and common patterns cover 80% of use cases
- Rule Reference - When you need advanced rule syntax (relational rules, transformations)
- Nextflow Reference - For Nextflow-specific work (custom language config, real examples)
- ast-grep docs - For edge cases: https://ast-grep.github.io/
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
zbench
Benchmark interactive zsh performance with zsh-bench and track regressions. Use when benchmarking shell startup, comparing zsh latency after config changes, investigating slow shell, or running git bisect on performance. Trigger phrases: "benchmark zsh", "shell is slow", "zbench", "zsh-bench", "shell startup time", "profile zsh", "zsh performance".
nix-rebuild
Rebuild nix-darwin/NixOS system after dotfiles changes. Use when config files managed by Nix (lazygit, ghostty, etc.) need to be regenerated, or after editing any .nix file in the dotfiles repo.
hass-config-flow
Interact with Home Assistant via the REST API on a NixOS host. Use when adding integrations, querying entities, managing config flows, creating API tokens, or automating HA setup programmatically. Also covers identifying device protocols (Matter, Zigbee, Thread, HomeKit) from the device registry. Trigger phrases: "add HA integration", "configure home assistant", "query HA entities", "create HA token", "HA REST API", "pair homekit", "set up matter in HA", "add spotify to HA", "is this device zigbee or thread", "what protocol is this device", "move devices to ZHA", "identify matter devices".
hass-declarative
Manage Home Assistant automations, scenes, and scripts declaratively via NixOS modules. Covers adding/editing/removing entities in the domain-based Nix structure, the ensureEnabled wrapper (initial_state enforcement), the sweep service that cleans orphaned entities, entity identity (IDs, slugs, unique_ids), the eval test assertions, and the build-time manifest. Trigger phrases: "add HA automation", "new scene", "new script", "remove automation", "declarative HA", "sweep unmanaged", "entity drift", "ghost entity", "orphaned automation", "HA domain file", "eval-automations test", "hass assertion", "ensureEnabled", "initial_state".
agenix-secrets
Create, edit, and wire up agenix-encrypted secrets in this dotfiles repo. Use when adding API keys, tokens, credentials, passwords, or any sensitive values to NixOS host configs. Trigger phrases: "add a secret", "encrypt with agenix", "new age secret", "hide this value", "agenix secret".
linear
Read-only Linear issue access via the Linear GraphQL API.
Didn't find tool you were looking for?