Class: Legion::CLI::Chat::Tools::ConsolidateMemory

Inherits:
Tools::Base
  • Object
show all
Defined in:
lib/legion/cli/chat/tools/consolidate_memory.rb

Constant Summary collapse

CONSOLIDATION_PROMPT =
<<~PROMPT
  You are a memory consolidation engine. Given a list of memory entries, produce a cleaned-up version that:

  1. Removes exact or near-duplicate entries (keep the most complete version)
  2. Merges entries about the same topic into a single clear statement
  3. Preserves all unique and valuable information
  4. Keeps entries concise — one line per memory
  5. Drops entries that are purely temporary or session-specific
  6. Preserves the most recent timestamp when merging

  Return ONLY the consolidated entries, one per line, each prefixed with "- ".
  Do NOT add headers, explanations, or commentary.
PROMPT

Class Method Summary collapse

Methods inherited from Tools::Base

deferred, deferred?, description, error_response, extension, handle_exception, input_schema, log, mcp_category, mcp_tier, runner, sticky, tags, text_response, tool_name, trigger_words

Class Method Details

.call(scope: 'project', dry_run: nil) ⇒ Object



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
# File 'lib/legion/cli/chat/tools/consolidate_memory.rb', line 42

def self.call(scope: 'project', dry_run: nil)
  dry_run = dry_run.to_s == 'true'
  scope_sym = scope.to_s == 'global' ? :global : :project

  entries = MemoryStore.list(scope: scope_sym)
  return "No memory entries found in #{scope} scope." if entries.empty?
  return "Only #{entries.size} entries — no consolidation needed." if entries.size < 3

  consolidated = consolidate_entries(entries)
  return 'Consolidation failed: could not generate summary.' unless consolidated

  new_entries = parse_consolidated(consolidated)
  removed = entries.size - new_entries.size

  if dry_run
    preview = new_entries.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n")
    "Preview (#{entries.size} -> #{new_entries.size}, #{removed} removed):\n\n#{preview}"
  else
    write_consolidated(new_entries, scope_sym)
    "Consolidated #{scope} memory: #{entries.size} -> #{new_entries.size} entries (#{removed} removed/merged)"
  end
rescue StandardError => e
  Legion::Logging.warn("ConsolidateMemory#execute failed: #{e.message}") if defined?(Legion::Logging)
  "Error consolidating memory: #{e.message}"
end

.consolidate_entries(entries) ⇒ Object



68
69
70
71
72
73
74
75
76
# File 'lib/legion/cli/chat/tools/consolidate_memory.rb', line 68

def self.consolidate_entries(entries)
  return nil unless defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat_direct)

  numbered = entries.map.with_index(1) { |e, i| "#{i}. #{e}" }.join("\n")

  session = Legion::LLM.chat_direct(model: nil, provider: nil)
  response = session.ask("#{CONSOLIDATION_PROMPT}\n\nCurrent entries:\n#{numbered}")
  response.content
end

.parse_consolidated(text) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/legion/cli/chat/tools/consolidate_memory.rb', line 78

def self.parse_consolidated(text)
  text.lines
      .map(&:strip)
      .select { |line| line.start_with?('- ') }
      .map { |line| line.sub(/\A- /, '').strip }
      .reject(&:empty?)
end

.write_consolidated(entries, scope_sym) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/legion/cli/chat/tools/consolidate_memory.rb', line 86

def self.write_consolidated(entries, scope_sym)
  path = scope_sym == :global ? MemoryStore.global_path : MemoryStore.project_path
  header = scope_sym == :global ? "# Global Memory\n" : "# Project Memory\n"
  timestamp = Time.now.strftime('%Y-%m-%d %H:%M')

  content = header
  content += "\n_Consolidated on #{timestamp}_\n"
  entries.each { |entry| content += "\n- #{entry}\n" }

  MemoryStore.send(:ensure_dir, path)
  File.write(path, content, encoding: 'utf-8')
end