kettle-jem is a collection of Ast::Merge::MergerConfig presets and utilities for gem templating. It provides pre-configured merge settings (signature generators, node typing, section classifiers) for common file types used in gem development — Ruby files (Gemfile, Appraisals, gemspec), Markdown (README, CHANGELOG), YAML, JSON, RBS, and Dotenv files.
Core Philosophy: Extract reusable merge configuration from kettle-dev into declarative presets and YAML recipes, so gem templating logic is composable and testable.
Repository: https://github.com/kettle-rb/kettle-jem Current Version: 1.0.0 Required Ruby: >= 3.2.0 (currently developed against Ruby 4.0.1)
CRITICAL: AI agents can reliably read terminal output when commands run in the background and the output is polled afterward. However, each terminal command should be treated as a fresh shell with no shared state.
Use this pattern:
- Run commands with background execution enabled.
- Fetch the output afterward.
- Make every command self-contained — do not rely on a previous
cd,export, alias, or shell function.
CRITICAL: The canonical project environment now lives in mise.toml, with local overrides in .env.local loaded via dotenvy.
✅ CORRECT — Run self-contained commands with mise exec:
mise exec -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -- bundle exec rspec✅ CORRECT — If you need shell syntax first, load the environment in the same command:
eval "$(mise env -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -s bash)" && bundle exec rspec❌ WRONG — Do not rely on a previous command changing directories:
cd /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem
bundle exec rspec❌ WRONG — A chained cd does not give directory-change hooks time to update the environment:
cd /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem && bundle exec rspec✅ PREFERRED — Use internal tools:
grep_searchinstead ofgrepcommandfile_searchinstead offindcommandread_fileinstead ofcatcommandlist_dirinstead oflscommandreplace_string_in_fileorcreate_fileinstead ofsed/ manual editing
❌ AVOID when possible:
run_in_terminalfor information gathering
Only use terminal for:
- Running tests (
bundle exec rspec) - Installing dependencies (
bundle install) - Git operations that require interaction
- Commands that actually need to execute (not just gather info)
❌ ABSOLUTELY FORBIDDEN:
bundle exec rspec 2>&1 | tail -50✅ CORRECT — Run the plain command and read the full output afterward:
mise exec -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -- bundle exec rspecKettle::Jem::Presets— Pre-configuredMergerConfigobjects for each file typePresets::Gemfile— Gemfile merging (destination-wins, template-wins)Presets::Gemspec— Gemspec mergingPresets::Appraisals— Appraisals file mergingPresets::Markdown— Markdown merging with fenced code block handlingPresets::Rakefile— Rakefile mergingPresets::Yaml— YAML file mergingPresets::Json— JSON file mergingPresets::Rbs— RBS (type signature) mergingPresets::Dotenv— Dotenv file merging
Kettle::Jem::Classifiers— AST node classifiers for section typingClassifiers::GemCall— Classifygemmethod callsClassifiers::GemGroup— Classify gem grouping blocksClassifiers::AppraisalBlock— Classify Appraisal blocksClassifiers::MethodDef— Classify method definitionsClassifiers::SourceCall— Classifysourcemethod calls
Kettle::Jem::Signatures— Signature generators for structural matchingKettle::Jem::RecipeLoader— YAML recipe loading and resolution- YAML Recipes — Declarative merge recipes for each file type (
recipes/*.yml)
| Gem | Role |
|---|---|
ast-merge (~> 4.0) |
Shared merge infrastructure (SmartMergerBase, MergerConfig, etc.) |
tree_haver (~> 5.0) |
Unified AST parsing adapter |
prism-merge (~> 2.0) |
Ruby file merging (Gemfile, gemspec, Rakefile, Appraisals) |
markly-merge (~> 1.0) |
Markdown merging |
markdown-merge (~> 1.0) |
Generic markdown merging |
json-merge (~> 1.1) |
JSON file merging |
psych-merge (~> 1.0) |
YAML file merging |
rbs-merge (~> 2.0) |
RBS file merging |
bash-merge (~> 2.0) |
Bash file merging |
dotenv-merge (~> 1.0) |
Dotenv file merging |
version_gem (~> 1.1) |
Version management |
IMPORTANT: This project lives in vendor/kettle-jem/ within the ast-merge workspace. It is a nested git project with its own .git/ directory. The grep_search tool CANNOT search inside nested git projects — use read_file and list_dir instead.
lib/kettle/jem/
├── classifiers/ # AST node classifiers
│ ├── appraisal_block.rb # Appraisal block classifier
│ ├── gem_call.rb # gem() call classifier
│ ├── gem_group.rb # Gem grouping classifier
│ ├── method_def.rb # Method definition classifier
│ └── source_call.rb # source() call classifier
├── classifiers.rb # Classifier loader
├── presets/ # MergerConfig presets
│ ├── appraisals.rb # Appraisals file preset
│ ├── base.rb # Base preset class
│ ├── dotenv.rb # Dotenv preset
│ ├── gemfile.rb # Gemfile preset
│ ├── gemspec.rb # Gemspec preset
│ ├── json.rb # JSON preset
│ ├── markdown.rb # Markdown preset
│ ├── rakefile.rb # Rakefile preset
│ ├── rbs.rb # RBS preset
│ └── yaml.rb # YAML preset
├── presets.rb # Preset loader
├── recipe_loader.rb # YAML recipe loader
├── recipes/ # YAML merge recipes
│ ├── appraisals/ # Appraisals recipes
│ │ └── signature_generator.rb
│ ├── appraisals.yml
│ ├── gemfile/ # Gemfile recipes
│ │ ├── signature_generator.rb
│ │ └── typing/
│ │ └── call_node.rb
│ ├── gemfile.yml
│ ├── gemspec/ # Gemspec recipes
│ │ └── signature_generator.rb
│ ├── gemspec.yml
│ ├── markdown/ # Markdown recipes
│ │ └── signature_generator.rb
│ ├── markdown.yml
│ ├── rakefile/ # Rakefile recipes
│ │ └── signature_generator.rb
│ └── rakefile.yml
├── signatures.rb # Signature generator helpers
└── version.rb # Version constant
spec/kettle/jem/
├── classifiers/ # Classifier specs
├── presets/ # Preset specs
├── signatures_spec.rb # Signature specs
mise exec -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -- bundle exec rspecSingle file (disable coverage threshold):
mise exec -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -- env K_SOUP_COV_MIN_HARD=false bundle exec rspec spec/kettle/jem/presets/gemfile_spec.rbmise exec -C /home/pboling/src/kettle-rb/ast-merge/vendor/kettle-jem -- bin/rake coverageEach preset provides class methods that return Ast::Merge::MergerConfig objects:
# Get a destination-wins config for Gemfile merging
config = Kettle::Jem::Presets::Gemfile.destination_wins
merger = Prism::Merge::SmartMerger.new(template, dest, **config.to_h)
result = merger.merge
# Get a template-wins config for Markdown merging
config = Kettle::Jem::Presets::Markdown.template_wins
merger = Markly::Merge::SmartMerger.new(template, dest, **config.to_h)Recipes are YAML files that declare merge configuration:
# recipes/gemfile.yml
signature_generator: "Kettle::Jem::Recipes::Gemfile::SignatureGenerator"
node_typing:
call_node: "Kettle::Jem::Recipes::Gemfile::Typing::CallNode"CRITICAL: All constructors and public API methods that accept keyword arguments MUST include **options as the final parameter.
kettle-jem extracts reusable merge presets and recipes that were previously embedded directly in kettle-dev's templating code. kettle-dev depends on kettle-jem for its merge configurations when performing template updates.
Use dependency tags from ast-merge to conditionally skip tests:
RSpec.describe SomeClass, :prism_merge do
# Skipped if prism-merge unavailable
endAll specs use require "kettle/test/rspec" for RSpec helpers (stubbed_env, block_is_expected, silent_stream, timecop).
| File | Purpose |
|---|---|
lib/kettle/jem/presets/gemfile.rb |
Gemfile merge preset |
lib/kettle/jem/presets/base.rb |
Base preset class |
lib/kettle/jem/classifiers/gem_call.rb |
gem() call classifier |
lib/kettle/jem/recipe_loader.rb |
YAML recipe loading |
lib/kettle/jem/signatures.rb |
Signature generators |
lib/kettle/jem/recipes/gemfile.yml |
Gemfile merge recipe |
mise.toml |
Shared development environment variables and local .env.local loading |
- NEVER add backward compatibility — No shims, aliases, or deprecation layers.
- NEVER expect
cdto persist — Every terminal command is isolated; use a self-containedmise exec -C ... -- ...invocation. - NEVER pipe test output through
head/tail— Run tests without truncation so you can inspect the full output. - Terminal commands do not share shell state — Previous
cd,export, aliases, and functions are not available to the next command. - Use
tmp/for temporary files — Never use/tmpor other system directories. - Presets return
MergerConfigobjects — Use.to_hto pass options to SmartMerger constructors.