Class: Ace::PromptPrep::Molecules::BundleLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/prompt_prep/molecules/bundle_loader.rb

Overview

Load bundle via ace-bundle Ruby API

This module calls Ace::Bundle.load_file and returns the result.

Constant Summary collapse

VALID_FORMATS =

Valid bundle format options

["markdown-xml", "markdown", "xml"].freeze

Class Method Summary collapse

Class Method Details

.call(prompt_path, options = {}) ⇒ String

Load bundle from prompt file with validation

Parameters:

  • prompt_path (String)

    Path to prompt file

  • options (Hash) (defaults to: {})

    Additional options

Options Hash (options):

  • :format (String)

    Output format (‘markdown-xml’, ‘markdown’, ‘xml’)

  • :embed_source (Boolean)

    Whether to embed source content

Returns:

  • (String)

    Bundle content or empty string on error



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/ace/prompt_prep/molecules/bundle_loader.rb', line 20

def self.call(prompt_path, options = {})
  # Input validation
  return "" unless valid_prompt_path?(prompt_path)
  return "" unless valid_options?(options)

  debug_log("Loading bundle from: #{prompt_path}", :bundle_loading)

  begin
    require "ace/bundle"
  rescue LoadError
    warn "Error: ace-bundle gem not available"
    return ""
  end

  begin
    # Validate that the prompt file exists and is readable
    unless File.exist?(prompt_path)
      warn "Error: Prompt file does not exist: #{prompt_path}"
      return ""
    end

    unless File.readable?(prompt_path)
      warn "Error: Prompt file is not readable: #{prompt_path}"
      return ""
    end

    # Resolve symlinks and validate project boundaries
    real_path = begin
      File.realpath(prompt_path)
    rescue
      prompt_path
    end
    project_root = Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current

    unless real_path.start_with?(project_root)
      warn "Error: File path resolves outside project: #{real_path}"
      return ""
    end

    # Check file size to prevent processing extremely large files
    file_size = File.size(prompt_path)
    max_size_bytes = (Ace::PromptPrep.config.dig("security", "max_file_size_mb") || 10) * 1024 * 1024
    debug_log("File size: #{file_size} bytes (limit: #{max_size_bytes / 1024 / 1024}MB)", :bundle_loading)
    if file_size > max_size_bytes
      warn "Error: Prompt file too large (#{file_size} bytes), exceeds limit of #{max_size_bytes / 1024 / 1024}MB"
      return ""
    end

    bundle_data = Ace::Bundle.load_file(
      prompt_path,
      format: options[:format] || "markdown-xml",
      embed_source: options[:embed_source].nil? || options[:embed_source]
    )

    # Validate bundle data structure
    if bundle_data.nil?
      warn "Warning: Bundle data is nil"
      return ""
    end

    # Extract content with validation
    content = bundle_data.content
    if content.nil?
      warn "Warning: Bundle content is nil"
      return ""
    end

    # Validate content type
    unless content.is_a?(String)
      warn "Warning: Bundle content is not a string (#{content.class})"
      return ""
    end

    # Validate content length
    if content.empty?
      warn "Warning: Bundle content is empty"
      return ""
    end

    debug_log("Bundle loaded successfully, content length: #{content.length} characters", :bundle_loading)
    content
  rescue => e
    warn "Error: Failed to load bundle: #{e.message}"
    ""
  end
end

.debug_log(message, category = nil) ⇒ Object

Debug logging for troubleshooting and development

Parameters:

  • message (String)

    Debug message

  • category (Symbol, nil) (defaults to: nil)

    Optional category for filtering



113
114
115
116
117
118
# File 'lib/ace/prompt_prep/molecules/bundle_loader.rb', line 113

def self.debug_log(message, category = nil)
  return unless Ace::PromptPrep.config.dig("debug", "enabled")
  return if category && !Ace::PromptPrep.config.dig("debug", category.to_s)

  warn "[DEBUG] #{message}"
end

.valid_options?(options) ⇒ Boolean

Validate options hash

Parameters:

  • options (Hash)

    Options to validate

Returns:

  • (Boolean)

    True if valid



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ace/prompt_prep/molecules/bundle_loader.rb', line 153

def self.valid_options?(options)
  return true unless options.is_a?(Hash)

  # Validate format option if provided
  if options.key?(:format)
    format = options[:format]
    unless format.is_a?(String) && VALID_FORMATS.include?(format)
      warn "Warning: Invalid format '#{format}', using default 'markdown-xml'"
      options[:format] = "markdown-xml"
    end
  end

  # Validate embed_source option if provided
  if options.key?(:embed_source)
    unless [true, false].include?(options[:embed_source])
      warn "Warning: Invalid embed_source value, using default true"
      options[:embed_source] = true
    end
  end

  true
end

.valid_prompt_path?(prompt_path) ⇒ Boolean

Validate prompt path input

Parameters:

  • prompt_path (String, nil)

    Path to validate

Returns:

  • (Boolean)

    True if valid



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/ace/prompt_prep/molecules/bundle_loader.rb', line 124

def self.valid_prompt_path?(prompt_path)
  return false if prompt_path.nil?
  return false if prompt_path.to_s.strip.empty?

  path_str = prompt_path.to_s

  # Check for various path traversal patterns
  return false if path_str.include?("../")
  return false if path_str.include?("..\\")
  return false if path_str.include?("%2e%2e")  # URL encoded
  return false if path_str.include?("..%2f")
  return false if path_str.include?("%2e%2e%2f")

  # Reject absolute paths (should be relative to project)
  return false if File.absolute_path?(path_str) && !path_str.start_with?("/")

  # Check for shell escape patterns
  return false if path_str.include?(";")
  return false if path_str.include?("&")
  return false if path_str.include?("|")
  return false if path_str.include?("`")

  true
end