Class: ClaudeMemory::Hook::ContextInjector

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/hook/context_injector.rb

Overview

Generates context for SessionStart hook injection. Queries both global and project databases for key facts and formats them as concise context for Claude.

Constant Summary collapse

MAX_DECISIONS =
5
MAX_CONVENTIONS =
5
MAX_ARCHITECTURE =
5
MAX_UNDISTILLED =
3
MAX_TEXT_PER_ITEM =
1500
MAX_MIRROR_CANDIDATES =
5
FRESH_SESSION_SOURCES =
%w[startup resume clear].freeze
QUERIES =
{
  decisions: {query: "decision constraint rule requirement", scope: "all"},
  conventions: {query: "convention style format pattern prefer", scope: "all"},
  architecture: {query: "uses framework implements architecture pattern", scope: "all"}
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(manager, source: nil, auto_memory_mirror: nil) ⇒ ContextInjector

Returns a new instance of ContextInjector.



35
36
37
38
39
40
41
42
43
# File 'lib/claude_memory/hook/context_injector.rb', line 35

def initialize(manager, source: nil, auto_memory_mirror: nil)
  @manager = manager
  @source = source
  @recall = Recall.new(manager)
  @auto_memory_mirror = auto_memory_mirror
  @emitted_fact_ids = []
  @emitted_subjects = []
  @emitted_facts_by_scope = Hash.new { |h, k| h[k] = [] }
end

Instance Attribute Details

#emitted_fact_idsObject (readonly)

Fact IDs and subjects that ‘generate_context` injected on the most recent call. Both are empty until `generate_context` has been invoked. Populated in call order (decisions → conventions → architecture) so benchmark harnesses can attribute sections if they care.

emitted_facts_by_scope groups the IDs by the DB they came from (=> […], “global” => […]) so telemetry can resolve each fact from the correct store. Fact IDs autoincrement per-DB, so a bare ID without scope is ambiguous.



33
34
35
# File 'lib/claude_memory/hook/context_injector.rb', line 33

def emitted_fact_ids
  @emitted_fact_ids
end

#emitted_facts_by_scopeObject (readonly)

Fact IDs and subjects that ‘generate_context` injected on the most recent call. Both are empty until `generate_context` has been invoked. Populated in call order (decisions → conventions → architecture) so benchmark harnesses can attribute sections if they care.

emitted_facts_by_scope groups the IDs by the DB they came from (=> […], “global” => […]) so telemetry can resolve each fact from the correct store. Fact IDs autoincrement per-DB, so a bare ID without scope is ambiguous.



33
34
35
# File 'lib/claude_memory/hook/context_injector.rb', line 33

def emitted_facts_by_scope
  @emitted_facts_by_scope
end

#emitted_subjectsObject (readonly)

Fact IDs and subjects that ‘generate_context` injected on the most recent call. Both are empty until `generate_context` has been invoked. Populated in call order (decisions → conventions → architecture) so benchmark harnesses can attribute sections if they care.

emitted_facts_by_scope groups the IDs by the DB they came from (=> […], “global” => […]) so telemetry can resolve each fact from the correct store. Fact IDs autoincrement per-DB, so a bare ID without scope is ambiguous.



33
34
35
# File 'lib/claude_memory/hook/context_injector.rb', line 33

def emitted_subjects
  @emitted_subjects
end

Instance Method Details

#generate_contextObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/claude_memory/hook/context_injector.rb', line 45

def generate_context
  @emitted_fact_ids = []
  @emitted_subjects = []
  @emitted_facts_by_scope = Hash.new { |h, k| h[k] = [] }
  sections = []

  decisions = fetch(:decisions, MAX_DECISIONS)
  sections << format_section("Decisions", decisions) if decisions.any?

  conventions = fetch(:conventions, MAX_CONVENTIONS)
  sections << format_section("Conventions", conventions) if conventions.any?

  architecture = fetch(:architecture, MAX_ARCHITECTURE)
  sections << format_section("Architecture", architecture) if architecture.any?

  if fresh_session?
    undistilled = fetch_undistilled(MAX_UNDISTILLED)
    sections << format_distillation_prompt(undistilled) if undistilled.any?

    mirror_candidates = fetch_mirror_candidates(MAX_MIRROR_CANDIDATES)
    if mirror_candidates.any?
      sections << format_auto_memory_mirror(mirror_candidates)
      auto_memory_mirror.commit(mirror_candidates)
    end
  end

  return nil if sections.empty?

  sections.join("\n")
end