Class: ClaudeMemory::Hook::ContextInjector
- Inherits:
-
Object
- Object
- ClaudeMemory::Hook::ContextInjector
- 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_OBSERVATIONS =
10- MAX_PROMOTION_CANDIDATES =
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
-
#emitted_fact_ids ⇒ Object
readonly
Fact IDs and subjects that ‘generate_context` injected on the most recent call.
-
#emitted_facts_by_scope ⇒ Object
readonly
Fact IDs and subjects that ‘generate_context` injected on the most recent call.
-
#emitted_observation_count ⇒ Object
readonly
Fact IDs and subjects that ‘generate_context` injected on the most recent call.
-
#emitted_subjects ⇒ Object
readonly
Fact IDs and subjects that ‘generate_context` injected on the most recent call.
Instance Method Summary collapse
- #generate_context ⇒ Object
-
#initialize(manager, source: nil, auto_memory_mirror: nil, stale_threshold_days: nil) ⇒ ContextInjector
constructor
A new instance of ContextInjector.
-
#reflection_context ⇒ String?
Reflection-only context for PreCompact (context pressure) — just the promote/consolidate instructions for corroborated/related observations.
Constructor Details
#initialize(manager, source: nil, auto_memory_mirror: nil, stale_threshold_days: nil) ⇒ ContextInjector
Returns a new instance of ContextInjector.
38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/claude_memory/hook/context_injector.rb', line 38 def initialize(manager, source: nil, auto_memory_mirror: nil, stale_threshold_days: nil) @manager = manager @source = source @recall = Recall.new(manager) @auto_memory_mirror = auto_memory_mirror @stale_threshold_days = stale_threshold_days @emitted_fact_ids = [] @emitted_subjects = [] @emitted_facts_by_scope = Hash.new { |h, k| h[k] = [] } @emitted_observation_count = 0 end |
Instance Attribute Details
#emitted_fact_ids ⇒ Object (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.
35 36 37 |
# File 'lib/claude_memory/hook/context_injector.rb', line 35 def emitted_fact_ids @emitted_fact_ids end |
#emitted_facts_by_scope ⇒ Object (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.
35 36 37 |
# File 'lib/claude_memory/hook/context_injector.rb', line 35 def emitted_facts_by_scope @emitted_facts_by_scope end |
#emitted_observation_count ⇒ Object (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.
35 36 37 |
# File 'lib/claude_memory/hook/context_injector.rb', line 35 def emitted_observation_count @emitted_observation_count end |
#emitted_subjects ⇒ Object (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.
35 36 37 |
# File 'lib/claude_memory/hook/context_injector.rb', line 35 def emitted_subjects @emitted_subjects end |
Instance Method Details
#generate_context ⇒ Object
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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/claude_memory/hook/context_injector.rb', line 50 def generate_context @emitted_fact_ids = [] @emitted_subjects = [] @emitted_facts_by_scope = Hash.new { |h, k| h[k] = [] } @emitted_observation_count = 0 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? # Block 1 of the two-block context: the episodic observation log. Sits # ahead of the (fresh-session) undistilled "Pending Knowledge Extraction" # tail (Block 2). Newest-first; only 🔴 carries a marker for the actor. observations = fetch_observations(MAX_OBSERVATIONS) @emitted_observation_count = observations.size obs_section = Observe::ObservationsRenderer.render(observations) sections << obs_section if obs_section if fresh_session? undistilled = fetch_undistilled(MAX_UNDISTILLED) sections << format_distillation_prompt(undistilled) if undistilled.any? promotion = fetch_promotion_candidates(MAX_PROMOTION_CANDIDATES) sections << format_observation_reflection(promotion) if promotion.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 |
#reflection_context ⇒ String?
Reflection-only context for PreCompact (context pressure) — just the promote/consolidate instructions for corroborated/related observations. PreCompact is the analog of Mastra’s token-threshold reflection trigger; at compaction we nudge the Claude-as-reflector pass rather than re-inject the full snapshot (which would add tokens as the window fills).
99 100 101 102 103 104 |
# File 'lib/claude_memory/hook/context_injector.rb', line 99 def reflection_context candidates = fetch_promotion_candidates(MAX_PROMOTION_CANDIDATES) return nil if candidates.empty? format_observation_reflection(candidates) end |