Module: RailsAiContext::Serializers::SectionMarkerWriter

Defined in:
lib/rails_ai_context/serializers/section_marker_writer.rb

Overview

Shared writer that wraps content in ‘<!– BEGIN/END rails-ai-context –>` markers so user-added content OUTSIDE the markers survives regeneration.

Used by ContextFileSerializer (for CLAUDE.md, AGENTS.md, .github/copilot- instructions.md) and CursorRulesSerializer (for .cursorrules). Same marker contract everywhere — a user can hand-write content above or below the gem-managed block and it’ll persist across ‘rails ai:context` runs.

Constant Summary collapse

BEGIN_MARKER =
"<!-- BEGIN rails-ai-context -->"
END_MARKER =
"<!-- END rails-ai-context -->"

Class Method Summary collapse

Class Method Details

.atomic_write(filepath, content) ⇒ Object

Write via temp file + rename so concurrent readers never see a partial.



54
55
56
57
58
59
60
# File 'lib/rails_ai_context/serializers/section_marker_writer.rb', line 54

def atomic_write(filepath, content)
  dir = File.dirname(filepath)
  FileUtils.mkdir_p(dir)
  tmp = File.join(dir, ".#{File.basename(filepath)}.#{SecureRandom.hex(4)}.tmp")
  File.write(tmp, content)
  File.rename(tmp, filepath)
end

.write_with_markers(filepath, content) ⇒ Object

Write ‘content` to `filepath` wrapped in markers. If the file already exists with markers, replace only the marker block (preserving user content outside). If the file exists WITHOUT markers, prepend the marker block so AI tools read our context first while keeping the user’s prior content intact below.

Returns :written if the file changed (created or block updated), :skipped if the file already had the exact same marker block.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rails_ai_context/serializers/section_marker_writer.rb', line 29

def write_with_markers(filepath, content)
  marked_content = "#{BEGIN_MARKER}\n#{content}\n#{END_MARKER}\n"

  if File.exist?(filepath)
    existing = File.read(filepath)

    new_content = if existing.include?(BEGIN_MARKER) && existing.include?(END_MARKER)
      existing.sub(
        /#{Regexp.escape(BEGIN_MARKER)}.*?#{Regexp.escape(END_MARKER)}\n?/m,
        marked_content
      )
    else
      "#{marked_content}\n#{existing}"
    end

    return :skipped if new_content == existing
    atomic_write(filepath, new_content)
    :written
  else
    atomic_write(filepath, marked_content)
    :written
  end
end