Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning,
and yes, platform and engine support are part of the public API.
Please file a bug if you notice a violation of semantic versioning.
Unreleased
Added
Changed
Deprecated
Removed
Fixed
Security
2.0.4 - 2026-02-22
- TAG: v2.0.4
- COVERAGE: 98.34% – 891/906 lines in 12 files
- BRANCH COVERAGE: 84.34% – 501/594 branches in 12 files
- 93.51% documented
Fixed
- Always preserve destination magic comments (
# frozen_string_literal: true,
# encoding: UTF-8, etc.) at the top of merged output, regardless of merge
preference. Magic comments are file-level metadata managed by Prism and must
not be lost when the template side lacks them (e.g. after filtering).
emit_dest_prefix_linesnow detects contiguous magic comments from the first
destination node’s leading comments, emits them before any template-only nodes
(Phase 1), and records the emitted line numbers soadd_node_to_resultand
merge_node_body_recursivelyskip them to prevent duplication. - Non-top-of-file magic comments (e.g. used as documentation) are left alone and
treated as regular comments. - Fix blank line preservation between magic comments and subsequent comments when
template preference is used. Gap lines between a stripped magic comment and the
next remaining comment are now correctly emitted from the template source. - Fix inter-node gap line preservation when a matched node is output from the
template source.perform_mergenow checks whether the output source’s
analysis had a trailing blank before advancinglast_output_dest_line, so
emit_dest_gap_linescorrectly emits dest gap lines that the template lacked.
2.0.3 - 2026-02-22
- TAG: v2.0.3
- COVERAGE: 98.81% – 829/839 lines in 12 files
- BRANCH COVERAGE: 85.71% – 456/532 branches in 12 files
- 93.51% documented
Fixed
- Fix blank lines between blocks being stripped during recursive body merging.
merge_node_body_recursivelyassembled its output (opening line, merged body,
closingend) without emitting the trailing blank line that separates
consecutive blocks.add_node_to_resultalready handles this for non-recursive
nodes, but the recursive path was missing the same logic. Now emits a trailing
blank line after the closingendwhen the source has one.
2.0.2 - 2026-02-22
- TAG: v2.0.2
- COVERAGE: 98.80% – 823/833 lines in 12 files
- BRANCH COVERAGE: 85.61% – 452/528 branches in 12 files
- 93.51% documented
Fixed
- Fix node duplication when merging files with inline trailing comments (e.g.,
gemspecadd_dependencylines with# ruby >= 3.2.0).add_node_to_result
output the full source line (which already includes inline comments via
analysis.line_at), then also iteratedtrailing_commentsand re-emitted any
comment on the same line — duplicating the entire line. Now skips trailing
comments whosestart_linefalls within the node’s own line range. This was the
root cause of everyadd_dependency/add_development_dependencybeing
duplicated in gemspec and gemfile merges when inline comments were present. - Prevent potential double-wrapping in
merge_node_body_recursively— store the
raw (unwrapped)signature_generatoras@raw_signature_generatorand pass it
(instead of the already-effective generator) to innerSmartMergerinstances.
This ensuresbuild_effective_signature_generatorwraps it only once when
node_typingis also configured.
2.0.1 - 2026-02-22
- TAG: v2.0.1
- COVERAGE: 98.80% – 820/830 lines in 12 files
- BRANCH COVERAGE: 85.55% – 450/526 branches in 12 files
- 93.51% documented
Added
-
SmartMerger#emit_dest_prefix_linespreserves magic comments (e.g.,# frozen_string_literal: true)
and blank lines that appear before the first AST node in the destination file -
SmartMerger#emit_dest_gap_linespreserves blank lines between consecutive top-level blocks
in the destination, preventing them from being silently stripped during merge
Changed
-
SmartMerger#merge_with_debugnow usesmerge_result(returnsMergeResultobject)
instead ofmerge(returnsString), sostatisticsanddecision_summaryare accessible -
SmartMerger#build_resultnow passestemplate_analysisanddest_analysisto
MergeResult.newfor consistency withSmartMergerBaseAPI
Removed
- Removed redundant
attr_reader :node_typingfromSmartMerger— already provided
bySmartMergerBase
Fixed
- Inter-node blank line stripping: blank lines between top-level blocks (e.g., between
appraiseblocks in Appraisals, betweengemcalls in Gemfiles) are now preserved
from the destination source during merge - Prefix line stripping: magic comments and blank lines before the first AST statement
(e.g.,# frozen_string_literal: truein Appraisal.root.gemfile) are now preserved
2.0.0 - 2026-02-19
- TAG: v2.0.0
- COVERAGE: 97.26% – 780/802 lines in 12 files
- BRANCH COVERAGE: 82.07% – 412/502 branches in 12 files
- 93.59% documented
Added
- Many new features inherited from
ast-merge, and updated behaviors. -
FileAnalysis: Added
#errorsmethod for compatibility with SmartMergerBase- Returns
@parse_result.errorsfor consistency with other FileAnalysis classes - Enables SmartMergerBase to properly create parse errors when
valid?is false
- Returns
- AGENTS.md
Changed
- appraisal2 v3.0.6
- kettle-test v1.0.10
- stone_checksums v1.0.3
- ast-merge v4.0.6
- tree_haver v5.0.5
- tree_stump v0.2.0
- fork no longer required, updates all applied upstream
-
SmartMerger: Added
**optionsfor forward compatibility- Now passes
node_typingexplicitly toSmartMergerBaseinstead of storing locally - Accepts additional options that may be added to base class in future
- Now passes
-
FileAnalysis: Added
**optionsfor forward compatibility- Accepts additional options that may be added in future
- Consistent with other
*-mergegems’ FileAnalysis classes
-
MergeResult: Added
**optionsfor forward compatibility -
ParseError: Updated constructor to accept base class signature
- Now accepts optional
message,errors:,content:, andparse_result:keywords - Compatible with
Ast::Merge::ParseErrorsignature while preservingparse_resultattribute - Enables SmartMergerBase to create parse errors without Prism-specific knowledge
- Now accepts optional
- Updated documentation on hostile takeover of RubyGems
- https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo
-
BREAKING: Error classes now inherit from
Ast::Mergebase classes:-
Prism::Merge::Errornow inherits fromAst::Merge::Error(wasStandardError) -
Prism::Merge::ParseErrornow inherits fromAst::Merge::ParseError(wasPrism::Merge::Error) -
ParseError#errorsattribute added (array of error objects fromparse_result.errors) - Code using
e.parse_result.errorsshould now usee.errorsdirectly -
parse_resultattribute is still available for Prism-specific access
-
1.1.6 - 2025-12-05
- TAG: v1.1.6
- COVERAGE: 98.31% – 929/945 lines in 9 files
- BRANCH COVERAGE: 87.08% – 391/449 branches in 9 files
- 100.00% documented
Fixed
-
Fixed duplicate content when freeze blocks precede nodes with leading comments: When a freeze block appeared before a node that had leading comments attached from earlier in the file, the merge would output duplicate content. Fixed by:
- Filtering out comments inside freeze blocks from being attached as leading comments to subsequent nodes
- Not including leading comments in anchor ranges when other nodes exist between the comments and the node
- Extending
extract_node_bodyto include content after the last statement up to the closing line, ensuring freeze blocks at the end of block bodies are preserved
1.1.5 - 2025-12-04
- TAG: v1.1.5
- COVERAGE: 98.26% – 906/922 lines in 9 files
- BRANCH COVERAGE: 87.27% – 384/440 branches in 9 files
- 100.00% documented
Changed
-
Recursive merge now preserves freeze blocks: When recursively merging nested block bodies (e.g.,
Gem::Specification.new do ... end), freeze blocks inside the body are now properly preserved. Previously, nested mergers were created withfreeze_token: nil, causing freeze blocks to be lost.
Fixed
-
Fixed freeze blocks lost in nested block bodies: Freeze blocks inside class, module, or call-with-block bodies were being lost during recursive merge. The fix passes
freeze_tokento nested mergers and ensuresextract_node_bodyincludes leading comments/freeze markers that appear between the node’s opening line and the first statement. - Fixed duplicate freeze markers in output: Freeze/unfreeze marker comments were incorrectly attached as leading comments to subsequent nodes, causing duplicate markers in merged output. These markers are now filtered from leading comments since they belong to FreezeNode boundaries.
1.1.4 - 2025-12-04
- TAG: v1.1.4
- COVERAGE: 98.26% – 902/918 lines in 9 files
- BRANCH COVERAGE: 87.59% – 381/435 branches in 9 files
- 100.00% documented
Added
-
Custom signature generator fallthrough support: Custom signature generators can now return a
Prism::NodeorFreezeNodeto fall through to the default signature computation. This allows custom generators to only override specific node types while delegating others to the built-in logic. Previously, returningnilwas the only way to skip custom handling, but that prevented proper matching for unhandled node types. -
Variable assignment node signatures: Added signature support for all variable write node types:
-
LocalVariableWriteNode→[:local_var, name] -
InstanceVariableWriteNode→[:ivar, name] -
ClassVariableWriteNode→[:cvar, name] -
GlobalVariableWriteNode→[:gvar, name] -
MultiWriteNode→[:multi_write, [target_names]]
-
Removed
- Removed pre-prism code in
ConflictResolverthat compared template node line numbers against destination freeze block line numbers (cross-file line comparison makes no sense)
Fixed
-
Fixed template-only nodes not being added when destination has freeze blocks: When
add_template_only_nodes: true, template nodes with no matching signature in destination were incorrectly skipped if the destination contained freeze blocks. The bug was caused by comparing template node line numbers against destination freeze block line numbers, which is a meaningless cross-file comparison. Template-only nodes are now correctly added regardless of freeze block presence.
1.1.3 - 2025-12-04
- TAG: v1.1.3
- COVERAGE: 96.71% – 881/911 lines in 9 files
- BRANCH COVERAGE: 82.86% – 348/420 branches in 9 files
- 100.00% documented
Fixed
-
Fixed indentation loss when adding nodes to merge result - the
add_nodemethod was usingnode.slicewhich loses leading indentation. Now usessource_analysis.line_atto get full lines from the source file, preserving original indentation.
1.1.2 - 2025-12-04
- TAG: v1.1.2
- COVERAGE: 96.66% – 868/898 lines in 9 files
- BRANCH COVERAGE: 82.84% – 338/408 branches in 9 files
- 100.00% documented
Added
-
body_has_mergeable_statements?private method to check if a block body contains statements that can be signature-matched -
mergeable_statement?private method to determine if a node type can generate signatures for merging -
max_recursion_depthoption (defaults toFloat::INFINITY) as a safety valve for edge cases
Fixed
-
Fixed infinite recursion when merging
CallNodeblocks (likegit_source) that have matching signatures but non-mergeable body content (e.g., just string literals). The fix detects when a block body contains only literals/expressions with no signature-matchable statements and treats the node atomically instead of recursing.
1.1.1 - 2025-12-04
- TAG: v1.1.1
- COVERAGE: 96.62% – 857/887 lines in 9 files
- BRANCH COVERAGE: 82.75% – 331/400 branches in 9 files
- 100.00% documented
Added
- Documented comparison of this tool, git-merge, and IDE “Smart Merge” in README.md
- Comprehensive node signature support for all Prism node types with nested content:
-
SingletonClassNode- singleton class definitions (class << self) -
CaseNode/CaseMatchNode- case statements and pattern matching -
WhileNode/UntilNode/ForNode- loop constructs -
BeginNode- exception handling blocks -
SuperNode/ForwardingSuperNode- super calls with blocks -
LambdaNode- lambda expressions -
PreExecutionNode/PostExecutionNode- BEGIN/END blocks -
ParenthesesNode/EmbeddedStatementsNode- parenthesized expressions
-
- Smart signature matching for assignment method calls (
config.setting = value) now matches by receiver and method name, not by value, enabling proper merging of configuration blocks
Changed
- Improved boundary ordering in merge timeline - destination-only content appearing after all template content now correctly appears at the end of merged output (was incorrectly appearing at the beginning)
- Extended recursive merge support to handle
SingletonClassNodeandBeginNodein addition to existingClassNode,ModuleNode, andCallNodetypes -
Freeze block validation expanded - freeze blocks can now be placed inside more container node types:
-
SingletonClassNode(class << self ... end) -
DefNode(method definitions) -
LambdaNode(lambda/proc definitions) -
CallNodewith blocks (e.g., RSpecdescribe/contextblocks) - This allows protecting portions of method implementations or DSL block contents
-
- Added README documentation comparing Prism::Merge algorithm to git merge and IDE smart merge strategies
- Added RBS type definitions for
FreezeNodeclass
Fixed
- Documentation of freeze blocks, and configuration to customize the freeze token
1.1.0 - 2025-12-04
- TAG: v1.1.0
- COVERAGE: 95.65% – 770/805 lines in 9 files
- BRANCH COVERAGE: 81.13% – 245/302 branches in 9 files
- 100.00% documented
Added
- Recursive merge support for class and module bodies - nested structures are now merged intelligently
- Conditional signature matching for
if/unlessblocks based on condition expression - Freeze block validation for partial/incomplete nodes and freeze blocks inside non-class/module contexts
- Freeze blocks now match by position/order when both files have multiple freeze blocks
-
add_template_only_nodesoption now properly respected in recursive merges and boundary processing -
DebugLogger, controlled byENV["PRISM_MERGE_DEBUG"]set to true or false - more specs
Changed
- Migrated to Prism v1.6.0 native comment attachment (removed custom comment association logic)
- Simplified FileAnalysis implementation using Prism’s built-in features
- Improved node lookup to handle anchors with leading comments (e.g., magic comments)
Fixed
- Template-only nodes are now correctly excluded in all contexts when
add_template_only_nodes: false - Freeze blocks inside methods now properly raise InvalidStructureError (only class/module-level freeze blocks allowed)
- Freeze block matching now works correctly with multiple consecutive freeze blocks (matches by index/order)
- Duplicate freeze blocks from template no longer appear when destination has matching freeze blocks
- Magic comments at file top no longer prevent node lookup in recursive merges
1.0.3 - 2025-12-03
- TAG: v1.0.3
- COVERAGE: 95.48% – 613/642 lines in 7 files
- BRANCH COVERAGE: 81.39% – 188/231 branches in 7 files
- 100.00% documented
Added
- Improved synopsis documentation
Fixed
- More fixes to Ruby compatibility documentation (down to Ruby 2.7)
1.0.2 - 2025-12-03
- TAG: v1.0.2
- COVERAGE: 95.48% – 613/642 lines in 7 files
- BRANCH COVERAGE: 81.39% – 188/231 branches in 7 files
- 100.00% documented
Added
- specs covering existing support for end-of-line comments
Fixed
- Ruby compatibility documentation
1.0.1 - 2025-12-03
- TAG: v1.0.1
- COVERAGE: 95.48% – 613/642 lines in 7 files
- BRANCH COVERAGE: 81.39% – 188/231 branches in 7 files
- 100.00% documented
Fixed
-
SmartMergernow correctly handles merge conflicts that involvekettle-dev:freezeblocks- Entire freeze block from destination is preserved
- Still allows template-only nodes to be added
- Resolves an issue where template-only nodes were dropped if destination contained a freeze block
1.0.0 - 2025-12-03
- TAG: v1.0.0
- COVERAGE: 95.44% – 607/636 lines in 7 files
- BRANCH COVERAGE: 81.94% – 186/227 branches in 7 files
- 100.00% documented
Added
- Initial release