Class: RailsAiBridge::Serializers::ContextFileSerializer

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/serializers/context_file_serializer.rb

Overview

Orchestrates writing context files to disk in various formats. Supports: CLAUDE.md, .cursorrules, .windsurfrules, .github/copilot-instructions.md, JSON Also generates split rule files for AI tools that support them.

Constant Summary collapse

FORMAT_MAP =
{
  claude: 'CLAUDE.md',
  codex: 'AGENTS.md',
  cursor: '.cursorrules',
  windsurf: '.windsurfrules',
  copilot: '.github/copilot-instructions.md',
  json: '.ai-context.json',
  gemini: 'GEMINI.md'
}.freeze
VALID_ON_CONFLICT_SYMBOLS =
%i[overwrite skip prompt].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, format: :all, split_rules: true, on_conflict: :overwrite) ⇒ ContextFileSerializer

Returns a new instance of ContextFileSerializer.

Parameters:

  • context (Hash)

    introspection context from RailsAiBridge.introspect

  • format (Symbol, Array<Symbol>) (defaults to: :all)

    format(s) to generate

  • split_rules (Boolean) (defaults to: true)

    whether to generate per-assistant rule directories

  • on_conflict (:overwrite, :skip, :prompt, #call) (defaults to: :overwrite)

    conflict resolution strategy; any object responding to +:call+ is invoked with the filepath and must return a truthy value to allow overwriting

Raises:

  • (ArgumentError)

    when +on_conflict+ is not a recognised symbol or callable



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/rails_ai_bridge/serializers/context_file_serializer.rb', line 30

def initialize(context, format: :all, split_rules: true, on_conflict: :overwrite)
  unless VALID_ON_CONFLICT_SYMBOLS.include?(on_conflict) || on_conflict.respond_to?(:call)
    raise ArgumentError,
          "on_conflict must be :overwrite, :skip, :prompt, or a callable; got #{on_conflict.inspect}"
  end

  @context     = context
  @format      = format
  @split_rules = split_rules
  @on_conflict = on_conflict
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



9
10
11
# File 'lib/rails_ai_bridge/serializers/context_file_serializer.rb', line 9

def context
  @context
end

#formatObject (readonly)

Returns the value of attribute format.



9
10
11
# File 'lib/rails_ai_bridge/serializers/context_file_serializer.rb', line 9

def format
  @format
end

#split_rulesObject (readonly)

Returns the value of attribute split_rules.



9
10
11
# File 'lib/rails_ai_bridge/serializers/context_file_serializer.rb', line 9

def split_rules
  @split_rules
end

Instance Method Details

#callHash{Symbol => Array<String>}

Write context files to the configured output directory, skipping unchanged ones.

Returns:

  • (Hash{Symbol => Array<String>})

    +:written+ paths and +:skipped+ paths

Raises:

  • (ArgumentError)

    when an unrecognised format symbol is encountered



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rails_ai_bridge/serializers/context_file_serializer.rb', line 46

def call
  formats = format == :all ? FORMAT_MAP.keys : Array(format)
  output_dir = RailsAiBridge.configuration.output_dir_for(Rails.application)
  written = []
  skipped = []

  formats.each do |fmt|
    filename = FORMAT_MAP[fmt]
    unless filename
      valid = FORMAT_MAP.keys.join(', ')
      raise ArgumentError, "Unknown format: #{fmt}. Valid formats: #{valid}"
    end

    filepath = File.join(output_dir, filename)

    # Ensure subdirectory exists (e.g. .github/)
    FileUtils.mkdir_p(File.dirname(filepath))

    content = serialize(fmt)

    file_exists = File.exist?(filepath)
    unchanged   = file_exists && File.read(filepath) == content
    if !unchanged && (!file_exists || overwrite?(filepath))
      File.write(filepath, content)
      written << filepath
    else
      skipped << filepath
    end
  end

  generate_split_rules(formats, output_dir, written, skipped) if split_rules

  { written: written, skipped: skipped }
end