Module: Brute::Truncation

Defined in:
lib/brute/truncation.rb

Overview

Universal tool output truncation.

Every tool result passes through Truncation.truncate() before entering the LLM context. This is the primary guard against context window explosion — even if a tool has no internal limits, this module caps the output to a safe size.

Existing features (ref: opencode truncate.ts):

  1. Line + byte dual cap — truncate when output exceeds MAX_LINES (2000) or MAX_BYTES (50 KB), whichever is hit first.

  2. Head mode (default) — keep the first N lines / bytes. Used for most tool output where the beginning is most relevant.

  3. Tail mode — keep the last N lines / bytes. Used for shell output where errors and summaries appear at the end.

  4. Overflow to disk — when truncating, write the full text to a file under TRUNCATION_DIR (e.g. ~/.local/share/brute/tool-output/). Return a preview + hint pointing to the saved file.

  5. Hint message — when truncated, append a contextual hint: “Full output saved to: <path>. Use Read with offset/limit to view specific sections.”

  6. Configurable limits — allow overriding MAX_LINES / MAX_BYTES via per-call options.

  7. Retention cleanup — purge saved output files older than a configurable retention period from a truncation directory.

  8. Per-line truncation — truncate individual lines longer than MAX_LINE_LENGTH (2000 chars) with a suffix.

Constant Summary collapse

MAX_LINES =
2000
MAX_BYTES =

50 KB

50 * 1024
MAX_LINE_LENGTH =
2000
TRUNCATION_MARKER =
"[Output truncated:"
TRUNCATION_DIR =
File.join(Dir.home, ".local", "share", "brute", "tool-output")

Class Method Summary collapse

Class Method Details

.already_truncated?(text) ⇒ Boolean

Check whether text already contains a truncation marker.

Returns:

  • (Boolean)


93
94
95
# File 'lib/brute/truncation.rb', line 93

def self.already_truncated?(text)
  text.include?(TRUNCATION_MARKER)
end

.cleanup!(dir, retention_days: 7) ⇒ Object

Purge files older than retention_days from the given directory.



104
105
106
107
108
109
110
# File 'lib/brute/truncation.rb', line 104

def self.cleanup!(dir, retention_days: 7)
  return unless File.directory?(dir)
  cutoff = Time.now - (retention_days * 86400)
  Dir.glob(File.join(dir, "*")).each do |path|
    File.delete(path) if File.file?(path) && File.mtime(path) < cutoff
  end
end

.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil) ⇒ String

Truncate text to fit within line and byte limits.

Returns the text unchanged if it fits. Otherwise returns a truncated preview with a hint message.

Parameters:

  • text (String)

    the tool output to truncate

  • max_lines (Integer) (defaults to: MAX_LINES)

    maximum number of lines to keep

  • max_bytes (Integer) (defaults to: MAX_BYTES)

    maximum byte size to keep

  • direction (:head, :tail) (defaults to: :head)

    which end to keep

  • truncation_dir (String, nil) (defaults to: nil)

    directory to save full output when truncating

Returns:

  • (String)

    the (possibly truncated) text



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
# File 'lib/brute/truncation.rb', line 57

def self.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil)
  return text if text.nil? || text.empty?

  # Per-line truncation first — cap individual lines
  lines = text.lines.map { |line| truncate_line(line) }
  text = lines.join

  return text if lines.size <= max_lines && text.bytesize <= max_bytes

  # Determine how many lines we can keep within both caps
  kept = direction == :tail ? lines.last(max_lines) : lines.first(max_lines)

  # Enforce byte cap
  result_lines = []
  bytes = 0
  kept.each do |line|
    break if bytes + line.bytesize > max_bytes
    result_lines << line
    bytes += line.bytesize
  end

  result = result_lines.join
  total = lines.size
  shown = result_lines.size

  # Overflow to disk — save the full output so it can be inspected later
  saved_path = save_to_disk(text, truncation_dir)

  hint = "\n#{TRUNCATION_MARKER} showing #{shown} of #{total} lines]"
  if saved_path
    hint += "\nFull output saved to: #{saved_path}. Use Read with offset/limit to view specific sections."
  end
  result + hint
end

.truncate_line(line, max: MAX_LINE_LENGTH) ⇒ Object

Truncate a single line if it exceeds MAX_LINE_LENGTH.



98
99
100
101
# File 'lib/brute/truncation.rb', line 98

def self.truncate_line(line, max: MAX_LINE_LENGTH)
  return line if line.length <= max
  line[0, max] + "... (line truncated to #{max} chars)\n"
end