// Expert in test-first development of production-quality OpenRewrite recipes for YAML manipulation using LST structure, visitor patterns, and JsonPath matching. Automatically activates when working with OpenRewrite recipe files or Java files in `src/main/java/**/rewrite/**` directories.
| name | rewrite-yaml |
| description | Expert in test-first development of production-quality OpenRewrite recipes for YAML manipulation using LST structure, visitor patterns, and JsonPath matching. Automatically activates when working with OpenRewrite recipe files or Java files in `src/main/java/**/rewrite/**` directories. |
Create production-quality OpenRewrite recipes for YAML manipulation using test-first development.
Core principle: Write tests first (RED), implement minimally (GREEN), apply OpenRewrite idioms (REFACTOR).
Do not use this skill for:
JAVA 8 COMPATIBILITY ONLY: Use traditional if-else, switch statements, explicit casting. NO switch expressions, pattern matching, var, text blocks, or Java 9+ features.
LICENSE HEADERS: Always check for {repository_root}/gradle/licenseHeader.txt when creating new recipe files. If this file exists, include its contents as the license header at the top of the generated Java recipe file. Remember to substitute ${year} with the current year (2025 or later as appropriate).
Write test cases with before/after YAML examples using OpenRewrite's testing framework.
Choose declarative (YAML composition) vs imperative (Java Visitor) approach.
Implement just enough to make tests pass.
Improve recipe using traits, composition, and conventions.
Add displayName, description (with markdown), and usage examples.
Use OpenRewrite's testing framework with before/after YAML. For detailed test patterns and examples, see ./references/testing-patterns.md.
Run tests to confirm RED state - tests must fail initially.
Key principle: Start with simplest possible before/after. Add complexity incrementally.
Start with: Can this be done by composing existing recipes?
Create YAML file in src/main/resources/META-INF/rewrite/:
---
type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyComposedRecipe
displayName: My composed recipe
description: Composes existing recipes
recipeList:
- org.openrewrite.yaml.search.FindKey:
keyPath: $.some.path
- org.openrewrite.yaml.ChangeValue:
keyPath: $.some.path
value: newValue
When to use declarative:
Common declarative recipes:
org.openrewrite.yaml.search.FindKey, FindValue - searchingorg.openrewrite.yaml.ChangeKey, ChangeValue, DeleteKey - modificationsorg.openrewrite.yaml.MergeYaml, CopyValue - additionsWhen to use imperative:
Key principle: Always try declarative first. Only go imperative when you hit limitations.
src/main/resources/META-INF/rewrite/org.openrewrite.yaml.* recipesFor common recipe patterns, see ./references/recipe-patterns.md.
Extend YamlIsoVisitor<ExecutionContext> (when not changing tree structure) or YamlVisitor<ExecutionContext> (when structure may change).
For complete recipe templates and examples, see ./references/recipe-template.java.
Automation: Use ./scripts/init_recipe.py <RecipeName> to generate recipe boilerplate (class, test file, YAML declarative option).
Run tests to achieve GREEN state - all tests must pass with formatting preserved.
1. Trait Usage
2. Recipe Composition
3. OpenRewrite Conventions
displayName and description (both support markdown)@Option annotations with descriptions./scripts/validate_java8.py src/ to check)null values and missing elements4. Performance Considerations
Recipe metadata (supports markdown):
displayName: User-friendly name with markdown formattingdescription: Detailed explanation with code examples, lists, links@Option descriptions: Clear explanations with inline code examplesExample with markdown:
@Override
public String getDisplayName() {
return "Update GitHub Actions to `actions/checkout@v4`";
}
@Override
public String getDescription() {
return "Updates all uses of `actions/checkout@v2` and `actions/checkout@v3` to `actions/checkout@v4`.\n\n" +
"**Before:**\n```yaml\n- uses: actions/checkout@v2\n```\n\n" +
"**After:**\n```yaml\n- uses: actions/checkout@v4\n```";
}
Usage examples:
Understanding the YAML LST hierarchy is essential. For detailed structure documentation, see ./references/yaml-lst-reference.md.
Key concepts:
For complete recipe templates and patterns, see:
./references/recipe-template.java - Complete recipe structure with annotations./references/recipe-patterns.md - Search, replacement, and modification patterns./references/jsonpath-patterns.md - Common JsonPath patterns for GitHub Actions, K8s, and generic YAMLKey matching:
if ("targetKey".equals(entry.getKey().getValue())) { /* match */ }
Safe value access:
String value = entry.getValue() instanceof Yaml.Scalar ?
((Yaml.Scalar) entry.getValue()).getValue() : null;
JsonPath matching:
JsonPathMatcher matcher = new JsonPathMatcher("$.jobs.*.steps[*].uses");
if (matcher.matches(getCursor())) { /* process */ }
For more JsonPath patterns, search ./references/jsonpath-patterns.md for your use case.
return super.visitMappingEntry(entry, ctx);withX() methods for all modificationsFor complex recipes that benefit from higher-level semantic abstractions, OpenRewrite Traits provide domain-specific logic by wrapping LST elements.
For detailed information about Traits, refer to the companion guide: ./references/openrewrite-traits-guide.md.
Quick Traits Overview:
Trait<T extends Tree> interfaceMatcher class extending SimpleTraitMatcher<T>matcher.asVisitor() to convert to TreeVisitor in recipesgetActionRef() instead of raw LST navigationExample using Traits:
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new ActionStep.Matcher().asVisitor((step, ctx) -> {
String ref = step.getActionRef();
if (ref != null && ref.contains("@v2")) {
return step.withActionRef(ref.replace("@v2", "@v3")).getTree();
}
return step.getTree();
});
}
For complete trait implementation patterns, matcher API details, and advanced examples, consult ./references/openrewrite-traits-guide.md.
com.yourorg.RecipeName)./scripts/validate_java8.py)gradle/licenseHeader.txt exists (use ./scripts/add_license_header.sh)Recipe is production-ready when:
Follow this workflow when creating OpenRewrite recipes:
Always provide complete, working recipes with proper annotations, error handling, and clear comments explaining the logic.