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)


109
110
111
# File 'lib/brute/truncation.rb', line 109

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.



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/brute/truncation.rb', line 123

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

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

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.



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

def self.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil)
  if text.nil? || text.empty?
    text
  else
    # Per-line truncation first — cap individual lines
    lines = text.lines.map { |line| truncate_line(line) }
    text = lines.join

    if lines.size <= max_lines && text.bytesize <= max_bytes
      text
    else
      # Determine how many lines we can keep within both caps
      if direction == :tail
        kept = lines.last(max_lines)
      else
        kept = lines.first(max_lines)
      end

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

.truncate_line(line, max: MAX_LINE_LENGTH) ⇒ Object

Truncate a single line if it exceeds MAX_LINE_LENGTH.



114
115
116
117
118
119
120
# File 'lib/brute/truncation.rb', line 114

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