Module: ClaudeMemory::MCP::Handlers::ManagementHandlers

Included in:
Tools
Defined in:
lib/claude_memory/mcp/handlers/management_handlers.rb

Overview

Management tool handlers (store_extraction, promote, sweep, changes, conflicts)

Instance Method Summary collapse

Instance Method Details

#changes(args) ⇒ Object



116
117
118
119
120
121
# File 'lib/claude_memory/mcp/handlers/management_handlers.rb', line 116

def changes(args)
  since = args["since"] || (Time.now - 86400 * 7).utc.iso8601
  scope = args["scope"] || "all"
  list = @recall.changes(since: since, limit: args["limit"] || 20, scope: scope)
  ResponseFormatter.format_changes(since, list)
end

#conflicts(args) ⇒ Object



123
124
125
126
127
# File 'lib/claude_memory/mcp/handlers/management_handlers.rb', line 123

def conflicts(args)
  scope = args["scope"] || "all"
  list = @recall.conflicts(scope: scope)
  ResponseFormatter.format_conflicts(list)
end

#mark_distilled(args) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/claude_memory/mcp/handlers/management_handlers.rb', line 129

def mark_distilled(args)
  content_item_id = args["content_item_id"]
  facts_extracted = args["facts_extracted"] || 0

  store = find_store_for_content_item(content_item_id)
  return {error: "Content item #{content_item_id} not found"} unless store

  store.record_ingestion_metrics(
    content_item_id: content_item_id,
    input_tokens: 0,
    output_tokens: 0,
    facts_extracted: facts_extracted
  )

  {
    success: true,
    content_item_id: content_item_id,
    facts_extracted: facts_extracted
  }
end

#promote(args) ⇒ Object



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

def promote(args)
  return {error: "Promote requires StoreManager"} unless @manager

  fact_id = args["fact_id"]
  global_fact_id = @manager.promote_fact(fact_id)

  if global_fact_id
    {
      success: true,
      project_fact_id: fact_id,
      global_fact_id: global_fact_id,
      message: "Fact promoted to global memory"
    }
  else
    {error: "Fact #{fact_id} not found in project database"}
  end
end

#reject_fact(args) ⇒ Object



77
78
79
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/handlers/management_handlers.rb', line 77

def reject_fact(args)
  scope = args["scope"] || "project"
  store = get_store_for_scope(scope)
  return {error: "Database not available"} unless store

  fact_id = args["fact_id"]
  if fact_id.nil? && args["docid"]
    row = store.find_fact_by_docid(args["docid"])
    fact_id = row && row[:id]
  end
  return {error: "fact_id or docid required"} if fact_id.nil?

  result = store.reject_fact(fact_id, reason: args["reason"])
  return {error: "Fact #{fact_id} not found in #{scope} database"} if result.nil?

  {
    success: true,
    scope: scope,
    fact_id: fact_id,
    conflicts_resolved: result[:conflicts_resolved],
    message: "Fact rejected"
  }
end

#store_extraction(args) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
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
# File 'lib/claude_memory/mcp/handlers/management_handlers.rb', line 8

def store_extraction(args)
  scope = args["scope"] || "project"
  store = get_store_for_scope(scope)
  return {error: "Database not available"} unless store

  entities = (args["entities"] || []).map { |e| symbolize_keys(e) }
  facts = (args["facts"] || []).map { |f| symbolize_keys(f) }
  decisions = (args["decisions"] || []).map { |d| symbolize_keys(d) }

  config = Configuration.new
  project_path = config.project_dir
  occurred_at = Time.now.utc.iso8601

  searchable_text = Core::TextBuilder.build_searchable_text(entities, facts, decisions)
  content_item_id = create_synthetic_content_item(store, searchable_text, project_path, occurred_at)
  index_content_item(store, content_item_id, searchable_text)

  extraction = Distill::Extraction.new(
    entities: entities,
    facts: facts,
    decisions: decisions,
    signals: []
  )

  # Guard against the LLM distiller labeling descriptions of external
  # projects (LOC counts, star counts, "X is a plugin by …") as
  # `convention`. Retag those as `reference` before resolution so
  # they don't pollute the Knowledge-base conventions list or get
  # returned by `memory.conventions`.
  extraction = Distill::ReferenceMaterialDetector.new.reclassify(extraction)

  resolver = Resolve::Resolver.new(store)
  result = resolver.apply(
    extraction,
    content_item_id: content_item_id,
    occurred_at: occurred_at,
    project_path: project_path,
    scope: scope
  )

  {
    success: true,
    scope: scope,
    content_item_id: content_item_id,
    entities_created: result[:entities_created],
    facts_created: result[:facts_created],
    facts_superseded: result[:facts_superseded],
    conflicts_created: result[:conflicts_created]
  }
end

#sweep_now(args) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/claude_memory/mcp/handlers/management_handlers.rb', line 101

def sweep_now(args)
  scope = args["scope"] || "project"
  store = get_store_for_scope(scope)
  return {error: "Database not available"} unless store

  sweeper = Sweep::Sweeper.new(store)
  budget = args["budget_seconds"] || 5
  stats = if args["escalate"]
    sweeper.run_with_escalation!(budget_seconds: budget)
  else
    sweeper.run!(budget_seconds: budget)
  end
  ResponseFormatter.format_sweep_stats(scope, stats)
end