Changelog

SemVer 2.0.0 Keep-A-Changelog 1.0.0

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_lines now 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 so add_node_to_result and
    merge_node_body_recursively skip 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_merge now checks whether the output source’s
    analysis had a trailing blank before advancing last_output_dest_line, so
    emit_dest_gap_lines correctly 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_recursively assembled its output (opening line, merged body,
    closing end) without emitting the trailing blank line that separates
    consecutive blocks. add_node_to_result already handles this for non-recursive
    nodes, but the recursive path was missing the same logic. Now emits a trailing
    blank line after the closing end when 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.,
    gemspec add_dependency lines 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 iterated trailing_comments and re-emitted any
    comment on the same line — duplicating the entire line. Now skips trailing
    comments whose start_line falls within the node’s own line range. This was the
    root cause of every add_dependency / add_development_dependency being
    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_generator as @raw_signature_generator and pass it
    (instead of the already-effective generator) to inner SmartMerger instances.
    This ensures build_effective_signature_generator wraps it only once when
    node_typing is 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_lines preserves 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_lines preserves blank lines between consecutive top-level blocks
    in the destination, preventing them from being silently stripped during merge

Changed

  • SmartMerger#merge_with_debug now uses merge_result (returns MergeResult object)
    instead of merge (returns String), so statistics and decision_summary are accessible
  • SmartMerger#build_result now passes template_analysis and dest_analysis to
    MergeResult.new for consistency with SmartMergerBase API

Removed

  • Removed redundant attr_reader :node_typing from SmartMerger — already provided
    by SmartMergerBase

Fixed

  • Inter-node blank line stripping: blank lines between top-level blocks (e.g., between
    appraise blocks in Appraisals, between gem calls 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: true in 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 #errors method for compatibility with SmartMergerBase
    • Returns @parse_result.errors for consistency with other FileAnalysis classes
    • Enables SmartMergerBase to properly create parse errors when valid? is false
  • 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 **options for forward compatibility
    • Now passes node_typing explicitly to SmartMergerBase instead of storing locally
    • Accepts additional options that may be added to base class in future
  • FileAnalysis: Added **options for forward compatibility
    • Accepts additional options that may be added in future
    • Consistent with other *-merge gems’ FileAnalysis classes
  • MergeResult: Added **options for forward compatibility
  • ParseError: Updated constructor to accept base class signature
    • Now accepts optional message, errors:, content:, and parse_result: keywords
    • Compatible with Ast::Merge::ParseError signature while preserving parse_result attribute
    • Enables SmartMergerBase to create parse errors without Prism-specific knowledge
  • 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::Merge base classes:
    • Prism::Merge::Error now inherits from Ast::Merge::Error (was StandardError)
    • Prism::Merge::ParseError now inherits from Ast::Merge::ParseError (was Prism::Merge::Error)
    • ParseError#errors attribute added (array of error objects from parse_result.errors)
    • Code using e.parse_result.errors should now use e.errors directly
    • parse_result attribute 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_body to 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 with freeze_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_token to nested mergers and ensures extract_node_body includes 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::Node or FreezeNode to 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, returning nil was 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 ConflictResolver that 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_node method was using node.slice which loses leading indentation. Now uses source_analysis.line_at to 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_depth option (defaults to Float::INFINITY) as a safety valve for edge cases

Fixed

  • Fixed infinite recursion when merging CallNode blocks (like git_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 SingletonClassNode and BeginNode in addition to existing ClassNode, ModuleNode, and CallNode types
  • 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)
    • CallNode with blocks (e.g., RSpec describe/context blocks)
    • 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 FreezeNode class

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/unless blocks 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_nodes option now properly respected in recursive merges and boundary processing
  • DebugLogger, controlled by ENV["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

  • SmartMerger now correctly handles merge conflicts that involve kettle-dev:freeze blocks
    • 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