AGENTS.md - bash-merge Development Guide
π― Project Overview
bash-merge is a format-specific implementation of the *-merge gem family for Bash shell scripts. It provides intelligent Bash script merging using AST analysis with tree-sitter Bash parser.
Core Philosophy: Intelligent Bash script merging that preserves structure, comments, and formatting while applying updates from templates.
Repository: https://github.com/kettle-rb/bash-merge
Current Version: 2.0.6
Required Ruby: >= 3.2.0 (currently developed against Ruby 4.0.1)
β οΈ AI Agent Terminal Limitations
Terminal Output Is Not Visible
CRITICAL: AI agents using run_in_terminal almost never see the command output. The terminal tool sends commands to a persistent Copilot terminal, but output is frequently lost or invisible to the agent.
Workaround: Always redirect output to a file in the projectβs local tmp/ directory, then read it back with read_file:
bundle exec rspec spec/some_spec.rb > tmp/test_output.txt 2>&1
NEVER use /tmp or other system directories β always use the projectβs own tmp/ directory.
direnv Requires Separate cd Command
CRITICAL: Never chain cd with other commands via &&. The direnv environment wonβt initialize until after all chained commands finish. Run cd alone first:
β CORRECT:
cd /home/pboling/src/kettle-rb/ast-merge/vendor/bash-merge
bundle exec rspec > tmp/test_output.txt 2>&1
β WRONG:
cd /home/pboling/src/kettle-rb/ast-merge/vendor/bash-merge && bundle exec rspec
Prefer Internal Tools Over Terminal
Use read_file, list_dir, grep_search, file_search instead of terminal commands for gathering information. Only use terminal for running tests, installing dependencies, and git operations.
grep_search Cannot Search Nested Git Projects
This project is a nested git project inside the ast-merge workspace. The grep_search tool cannot search inside it. Use read_file and list_dir instead.
NEVER Pipe Test Commands Through head/tail
Always redirect to a file in tmp/ instead of truncating output.
ποΈ Architecture: Format-Specific Implementation
What bash-merge Provides
-
Bash::Merge::SmartMergerβ Bash-specific SmartMerger implementation -
Bash::Merge::FileAnalysisβ Bash file analysis with function/command extraction -
Bash::Merge::NodeWrapperβ Wrapper for Bash AST nodes -
Bash::Merge::MergeResultβ Bash-specific merge result -
Bash::Merge::ConflictResolverβ Bash conflict resolution -
Bash::Merge::FreezeNodeβ Bash freeze block support -
Bash::Merge::DebugLoggerβ Bash-specific debug logging
Key Dependencies
| Gem | Role |
|---|---|
ast-merge (~> 4.0) |
Base classes and shared infrastructure |
tree_haver (~> 5.0) |
Unified parser adapter (tree-sitter) |
version_gem (~> 1.1) |
Version management |
Parser Backend Support
bash-merge works with tree-sitter Bash parser via TreeHaver:
| Backend | Parser | Platform | Notes |
|---|---|---|---|
:mri |
tree-sitter-bash | MRI only | Best performance, requires native library |
:rust |
tree-sitter-bash | MRI only | Rust implementation via tree_stump |
π Project Structure
lib/bash/merge/
βββ smart_merger.rb # Main SmartMerger implementation
βββ file_analysis.rb # Bash file analysis
βββ node_wrapper.rb # AST node wrapper
βββ merge_result.rb # Merge result object
βββ conflict_resolver.rb # Conflict resolution
βββ freeze_node.rb # Freeze block support
βββ debug_logger.rb # Debug logging
βββ version.rb
spec/bash/merge/
βββ smart_merger_spec.rb
βββ file_analysis_spec.rb
βββ node_wrapper_spec.rb
βββ integration/
π§ Development Workflows
Running Tests
# Full suite
bundle exec rspec
# Single file (disable coverage threshold check)
K_SOUP_COV_MIN_HARD=false bundle exec rspec spec/bash/merge/smart_merger_spec.rb
# Specific backend tests
bundle exec rspec --tag mri_backend
bundle exec rspec --tag rust_backend
bundle exec rspec --tag bash_grammar
Coverage Reports
cd /home/pboling/src/kettle-rb/ast-merge/vendor/bash-merge
bin/rake coverage && bin/kettle-soup-cover -d
π Project Conventions
API Conventions
SmartMerger API
-
mergeβ Returns a String (the merged Bash content) -
merge_resultβ Returns a MergeResult object -
to_son MergeResult returns the merged content as a string
Bash-Specific Features
Function Merging:
# Template
function setup() {
echo "Setting up..."
}
# Destination with customization
function setup() {
echo "Custom setup..."
}
Freeze Blocks:
# bash-merge:freeze
export CUSTOM_VAR="don't override"
alias custom="my custom alias"
# bash-merge:unfreeze
function main() {
echo "Hello"
}
π§ͺ Testing Patterns
TreeHaver Dependency Tags
Available tags:
-
:bash_grammarβ Requires Bash grammar (any backend) -
:mri_backendβ Requires tree-sitter MRI backend -
:rust_backendβ Requires tree-sitter Rust backend -
:bash_parsingβ Requires any Bash parser
β CORRECT:
RSpec.describe Bash::Merge::SmartMerger, :bash_grammar do
# Skipped if no Bash parser available
end
β WRONG:
before do
skip "Requires tree-sitter" unless bash_available? # DO NOT DO THIS
end
π‘ Key Insights
- Function matching: Bash functions matched by name
- Variable/export handling: Variable assignments can be tracked
- Comment preservation: Bash comments preserved and associated with statements
-
Freeze blocks use
# bash-merge:freeze: Standard comment syntax -
Shebang preservation:
#!/bin/bashis preserved at top of file - Backend isolation: MRI and Rust backends available
π« Common Pitfalls
-
NEVER mix FFI and MRI backends β Use
TreeHaver.with_backendfor isolation -
NEVER use manual skip checks β Use dependency tags (
:bash_grammar,:mri_backend) - Do NOT load vendor gems β They are not part of this project; they do not exist in CI
-
Use
tmp/for temporary files β Never use/tmpor other system directories
π§ Bash-Specific Notes
Node Types
function_definition # function name() { }
command # Simple commands
pipeline # cmd1 | cmd2
variable_assignment # VAR=value
export_statement # export VAR=value
Merge Behavior
- Functions: Matched by function name; entire function replaced
- Exports: Matched by variable name
- Commands: Position-based matching
- Comments: Preserved when attached to statements
- Freeze blocks: Protect customizations from template updates
- Shebang: Always preserved at top of file