Class: ClaudeMemory::MCP::ResponseFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/mcp/response_formatter.rb

Overview

Pure logic for formatting domain objects into MCP tool responses Follows Functional Core pattern - no I/O, just transformations

Class Method Summary collapse

Class Method Details

.append_line_range(result, receipt) ⇒ Object

Append stored line range from provenance if available

Parameters:

  • result (Hash)

    Result hash to append to

  • receipt (Hash)

    Receipt with optional :line_start, :line_end



159
160
161
162
163
164
# File 'lib/claude_memory/mcp/response_formatter.rb', line 159

def self.append_line_range(result, receipt)
  return unless receipt[:line_start]

  result[:line_start] = receipt[:line_start]
  result[:line_end] = receipt[:line_end]
end

.append_snippet(result, receipt, query) ⇒ Object

Extract and append snippet from raw_text if available

Parameters:

  • result (Hash)

    Result hash to append to

  • receipt (Hash)

    Receipt with optional :raw_text

  • query (String, nil)

    Query for snippet extraction



170
171
172
173
174
175
176
177
178
179
# File 'lib/claude_memory/mcp/response_formatter.rb', line 170

def self.append_snippet(result, receipt, query)
  return unless query && receipt[:raw_text]

  extracted = Core::SnippetExtractor.extract_with_lines(receipt[:raw_text], query)
  return unless extracted

  result[:snippet] = extracted[:snippet]
  result[:line_start] = extracted[:line_start]
  result[:line_end] = extracted[:line_end]
end

.format_change(change) ⇒ Hash

Format single change record

Parameters:

  • change (Hash)

    Change with fact fields

Returns:

  • (Hash)

    Formatted change



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/claude_memory/mcp/response_formatter.rb', line 195

def self.format_change(change)
  {
    id: change[:id],
    docid: change[:docid],
    predicate: change[:predicate],
    object: change[:object_literal],
    status: change[:status],
    created_at: change[:created_at],
    created_ago: Core::RelativeTime.format(change[:created_at]),
    source: change[:source]
  }
end

.format_changes(since, changes) ⇒ Hash

Format changes list into MCP response

Parameters:

  • since (String)

    ISO timestamp

  • changes (Array<Hash>)

    Change records

Returns:

  • (Hash)

    MCP response with since and formatted changes



185
186
187
188
189
190
# File 'lib/claude_memory/mcp/response_formatter.rb', line 185

def self.format_changes(since, changes)
  {
    since: since,
    changes: changes.map { |c| format_change(c) }
  }
end

.format_concept_fact(result, compact: false, query: nil) ⇒ Hash

Format single concept search fact with multi-concept similarity

Parameters:

  • result (Hash)

    Result with average and per-concept similarities

  • compact (Boolean) (defaults to: false)

    Omit receipts

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

    Original query for snippet extraction

Returns:

  • (Hash)

    Formatted fact with concept similarities



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/claude_memory/mcp/response_formatter.rb', line 307

def self.format_concept_fact(result, compact: false, query: nil)
  fact = {
    id: result[:fact][:id],
    docid: result[:fact][:docid],
    subject: result[:fact][:subject_name],
    predicate: result[:fact][:predicate],
    object: result[:fact][:object_literal],
    scope: result[:fact][:scope],
    source: result[:source],
    average_similarity: result[:similarity],
    concept_similarities: result[:concept_similarities]
  }
  fact[:receipts] = result[:receipts].map { |r| format_receipt(r, query: query) } unless compact
  fact
end

.format_concept_results(concepts, scope, results, compact: false) ⇒ Hash

Format concept search results

Parameters:

  • concepts (Array<String>)

    Concepts searched

  • scope (String)

    Scope

  • results (Array<Hash>)

    Results with similarity scores

  • compact (Boolean) (defaults to: false)

    Omit receipts for smaller responses

Returns:

  • (Hash)

    Formatted concept search response



292
293
294
295
296
297
298
299
300
# File 'lib/claude_memory/mcp/response_formatter.rb', line 292

def self.format_concept_results(concepts, scope, results, compact: false)
  query = concepts.join(" ")
  {
    concepts: concepts,
    scope: scope,
    count: results.size,
    facts: results.map { |r| format_concept_fact(r, compact: compact, query: query) }
  }
end

.format_conflict(conflict) ⇒ Hash

Format single conflict record

Parameters:

  • conflict (Hash)

    Conflict with fact IDs

Returns:

  • (Hash)

    Formatted conflict



221
222
223
224
225
226
227
228
229
# File 'lib/claude_memory/mcp/response_formatter.rb', line 221

def self.format_conflict(conflict)
  {
    id: conflict[:id],
    fact_a: conflict[:fact_a_id],
    fact_b: conflict[:fact_b_id],
    status: conflict[:status],
    source: conflict[:source]
  }
end

.format_conflicts(conflicts) ⇒ Hash

Format conflicts list into MCP response

Parameters:

  • conflicts (Array<Hash>)

    Conflict records

Returns:

  • (Hash)

    MCP response with count and formatted conflicts



211
212
213
214
215
216
# File 'lib/claude_memory/mcp/response_formatter.rb', line 211

def self.format_conflicts(conflicts)
  {
    count: conflicts.size,
    conflicts: conflicts.map { |c| format_conflict(c) }
  }
end

.format_context_facts(context_type, context_value, scope, results) ⇒ Hash

Format facts_by_context query results

Parameters:

  • context_type (String)

    Type (git_branch, cwd)

  • context_value (String)

    Value

  • scope (String)

    Scope

  • results (Array<Hash>)

    Query results

Returns:

  • (Hash)

    Formatted context facts response



370
371
372
373
374
375
376
377
378
# File 'lib/claude_memory/mcp/response_formatter.rb', line 370

def self.format_context_facts(context_type, context_value, scope, results)
  {
    context_type: context_type,
    context_value: context_value,
    scope: scope,
    count: results.size,
    facts: results.map { |r| format_generic_fact(r) }
  }
end

.format_detailed_explanation(explanation) ⇒ Hash

Format detailed explanation for recall_details response

Parameters:

  • explanation (Hash)

    Explanation with full relationships

Returns:

  • (Hash)

    Detailed fact response



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/claude_memory/mcp/response_formatter.rb', line 104

def self.format_detailed_explanation(explanation)
  {
    fact: {
      id: explanation[:fact][:id],
      docid: explanation[:fact][:docid],
      subject: explanation[:fact][:subject_name],
      predicate: explanation[:fact][:predicate],
      object: explanation[:fact][:object_literal],
      status: explanation[:fact][:status],
      confidence: explanation[:fact][:confidence],
      scope: explanation[:fact][:scope],
      valid_from: explanation[:fact][:valid_from],
      valid_from_ago: Core::RelativeTime.format(explanation[:fact][:valid_from]),
      valid_to: explanation[:fact][:valid_to]
    },
    receipts: explanation[:receipts].map { |r| format_detailed_receipt(r) },
    relationships: {
      supersedes: explanation[:supersedes],
      superseded_by: explanation[:superseded_by],
      conflicts: explanation[:conflicts].map { |c| {id: c[:id], status: c[:status]} }
    }
  }
end

.format_detailed_receipt(receipt, query: nil) ⇒ Hash

Format detailed receipt with session and timestamp

Parameters:

  • receipt (Hash)

    Receipt with full fields

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

    Optional query for snippet extraction

Returns:

  • (Hash)

    Formatted detailed receipt



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/claude_memory/mcp/response_formatter.rb', line 143

def self.format_detailed_receipt(receipt, query: nil)
  result = {
    quote: receipt[:quote],
    strength: receipt[:strength],
    session_id: receipt[:session_id],
    occurred_at: receipt[:occurred_at],
    occurred_ago: Core::RelativeTime.format(receipt[:occurred_at])
  }
  append_line_range(result, receipt)
  append_snippet(result, receipt, query)
  result
end

.format_explanation(explanation, scope) ⇒ Hash

Format explanation with full fact details and relationships

Parameters:

  • explanation (Hash)

    Explanation with :fact, :receipts, :supersedes, etc.

  • scope (String)

    Source scope

Returns:

  • (Hash)

    MCP response with fact, receipts, and relationships



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/claude_memory/mcp/response_formatter.rb', line 80

def self.format_explanation(explanation, scope)
  {
    fact: {
      id: explanation[:fact][:id],
      docid: explanation[:fact][:docid],
      subject: explanation[:fact][:subject_name],
      predicate: explanation[:fact][:predicate],
      object: explanation[:fact][:object_literal],
      status: explanation[:fact][:status],
      valid_from: explanation[:fact][:valid_from],
      valid_from_ago: Core::RelativeTime.format(explanation[:fact][:valid_from]),
      valid_to: explanation[:fact][:valid_to]
    },
    source: scope,
    receipts: explanation[:receipts].map { |p| format_receipt(p) },
    supersedes: explanation[:supersedes],
    superseded_by: explanation[:superseded_by],
    conflicts: explanation[:conflicts].map { |c| c[:id] }
  }
end

.format_generic_fact(result) ⇒ Hash

Format generic fact with scope and receipts

Parameters:

  • result (Hash)

    Result with fact and receipts

Returns:

  • (Hash)

    Formatted fact



383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/claude_memory/mcp/response_formatter.rb', line 383

def self.format_generic_fact(result)
  {
    id: result[:fact][:id],
    docid: result[:fact][:docid],
    subject: result[:fact][:subject_name],
    predicate: result[:fact][:predicate],
    object: result[:fact][:object_literal],
    scope: result[:fact][:scope],
    source: result[:source],
    receipts: result[:receipts].map { |r| format_receipt(r) }
  }
end

.format_index_fact(result) ⇒ Hash

Format single index fact with preview

Parameters:

  • result (Hash)

    Index result with fact data and token estimate

Returns:

  • (Hash)

    Formatted fact for index response



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/claude_memory/mcp/response_formatter.rb', line 61

def self.format_index_fact(result)
  {
    id: result[:id],
    docid: result[:docid],
    subject: result[:subject],
    predicate: result[:predicate],
    object_preview: result[:object_preview],
    status: result[:status],
    scope: result[:scope],
    confidence: result[:confidence],
    tokens: result[:token_estimate],
    source: result[:source]
  }
end

.format_index_results(query, scope, results) ⇒ Hash

Format index query results with token estimates

Parameters:

  • query (String)

    Original query

  • scope (String)

    Scope used

  • results (Array<Hash>)

    Index results with fact data

Returns:

  • (Hash)

    MCP response with metadata and facts



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/claude_memory/mcp/response_formatter.rb', line 46

def self.format_index_results(query, scope, results)
  total_tokens = results.sum { |r| r[:token_estimate] }

  {
    query: query,
    scope: scope,
    result_count: results.size,
    total_estimated_tokens: total_tokens,
    facts: results.map { |r| format_index_fact(r) }
  }
end

.format_recall_fact(result, compact: false, query: nil) ⇒ Hash

Format single recall fact result

Parameters:

  • result (Hash)

    Single result with :fact, :receipts, :source

  • compact (Boolean) (defaults to: false)

    Omit receipts

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

    Original query for snippet extraction

Returns:

  • (Hash)

    Formatted fact for MCP response



27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/claude_memory/mcp/response_formatter.rb', line 27

def self.format_recall_fact(result, compact: false, query: nil)
  fact = {
    id: result[:fact][:id],
    docid: result[:fact][:docid],
    subject: result[:fact][:subject_name],
    predicate: result[:fact][:predicate],
    object: result[:fact][:object_literal],
    status: result[:fact][:status],
    source: result[:source]
  }
  fact[:receipts] = result[:receipts].map { |p| format_receipt(p, query: query) } unless compact
  fact
end

.format_recall_results(results, compact: false, query: nil) ⇒ Hash

Format recall query results into MCP response

Parameters:

  • results (Array<Hash>)

    Recall results with :fact and :receipts

  • compact (Boolean) (defaults to: false)

    Omit receipts for smaller responses

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

    Original query for snippet extraction

Returns:

  • (Hash)

    MCP response with facts array



16
17
18
19
20
# File 'lib/claude_memory/mcp/response_formatter.rb', line 16

def self.format_recall_results(results, compact: false, query: nil)
  {
    facts: results.map { |r| format_recall_fact(r, compact: compact, query: query) }
  }
end

.format_receipt(receipt, query: nil) ⇒ Hash

Format receipt (provenance) with minimal fields

Parameters:

  • receipt (Hash)

    Receipt with :quote and :strength

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

    Optional query for snippet extraction

Returns:

  • (Hash)

    Formatted receipt



132
133
134
135
136
137
# File 'lib/claude_memory/mcp/response_formatter.rb', line 132

def self.format_receipt(receipt, query: nil)
  result = {quote: receipt[:quote], strength: receipt[:strength]}
  append_line_range(result, receipt)
  append_snippet(result, receipt, query)
  result
end

.format_semantic_fact(result, compact: false, query: nil) ⇒ Hash

Format single semantic search fact with similarity

Parameters:

  • result (Hash)

    Result with fact, receipts, and similarity

  • compact (Boolean) (defaults to: false)

    Omit receipts

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

    Original query for snippet extraction

Returns:

  • (Hash)

    Formatted fact with similarity



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/claude_memory/mcp/response_formatter.rb', line 270

def self.format_semantic_fact(result, compact: false, query: nil)
  fact = {
    id: result[:fact][:id],
    docid: result[:fact][:docid],
    subject: result[:fact][:subject_name],
    predicate: result[:fact][:predicate],
    object: result[:fact][:object_literal],
    scope: result[:fact][:scope],
    source: result[:source],
    similarity: result[:similarity]
  }
  fact[:score_trace] = result[:score_trace] if result[:score_trace]
  fact[:receipts] = result[:receipts].map { |r| format_receipt(r, query: query) } unless compact
  fact
end

.format_semantic_results(query, mode, scope, results, compact: false) ⇒ Hash

Format semantic search results with similarity scores

Parameters:

  • query (String)

    Search query

  • mode (String)

    Search mode (vector, text, both)

  • scope (String)

    Scope

  • results (Array<Hash>)

    Results with similarity scores

  • compact (Boolean) (defaults to: false)

    Omit receipts for smaller responses

Returns:

  • (Hash)

    Formatted semantic search response



255
256
257
258
259
260
261
262
263
# File 'lib/claude_memory/mcp/response_formatter.rb', line 255

def self.format_semantic_results(query, mode, scope, results, compact: false)
  {
    query: query,
    mode: mode,
    scope: scope,
    count: results.size,
    facts: results.map { |r| format_semantic_fact(r, compact: compact, query: query) }
  }
end

.format_shortcut_fact(result) ⇒ Hash

Format fact for shortcut queries (includes scope, no status)

Parameters:

  • result (Hash)

    Result with fact data

Returns:

  • (Hash)

    Formatted fact



338
339
340
341
342
343
344
345
346
347
348
# File 'lib/claude_memory/mcp/response_formatter.rb', line 338

def self.format_shortcut_fact(result)
  {
    id: result[:fact][:id],
    docid: result[:fact][:docid],
    subject: result[:fact][:subject_name],
    predicate: result[:fact][:predicate],
    object: result[:fact][:object_literal],
    scope: result[:fact][:scope],
    source: result[:source]
  }
end

.format_shortcut_results(category, results) ⇒ Hash

Format shortcut query results (decisions, architecture, etc.)

Parameters:

  • category (String)

    Shortcut category name

  • results (Array<Hash>)

    Query results

Returns:

  • (Hash)

    Formatted shortcut response



327
328
329
330
331
332
333
# File 'lib/claude_memory/mcp/response_formatter.rb', line 327

def self.format_shortcut_results(category, results)
  {
    category: category,
    count: results.size,
    facts: results.map { |r| format_shortcut_fact(r) }
  }
end

.format_sweep_stats(scope, stats) ⇒ Hash

Format sweep statistics into MCP response

Parameters:

  • scope (String)

    Database scope swept

  • stats (Hash)

    Sweeper stats

Returns:

  • (Hash)

    Formatted sweep response



235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/claude_memory/mcp/response_formatter.rb', line 235

def self.format_sweep_stats(scope, stats)
  result = {
    scope: scope,
    proposed_expired: stats[:proposed_facts_expired],
    disputed_expired: stats[:disputed_facts_expired],
    orphaned_deleted: stats[:orphaned_provenance_deleted],
    content_pruned: stats[:old_content_pruned],
    elapsed_seconds: stats[:elapsed_seconds].round(3)
  }
  result[:escalation_level] = stats[:escalation_level].to_s if stats[:escalation_level]
  result
end

.format_tool_facts(tool_name, scope, results) ⇒ Hash

Format facts_by_tool query results

Parameters:

  • tool_name (String)

    Tool name

  • scope (String)

    Scope

  • results (Array<Hash>)

    Query results

Returns:

  • (Hash)

    Formatted tool facts response



355
356
357
358
359
360
361
362
# File 'lib/claude_memory/mcp/response_formatter.rb', line 355

def self.format_tool_facts(tool_name, scope, results)
  {
    tool_name: tool_name,
    scope: scope,
    count: results.size,
    facts: results.map { |r| format_generic_fact(r) }
  }
end