lex-anchoring 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 69728f9f687e438684ae064c6d9e4af0143f0c045180b24741fd6411fac09e8f
4
+ data.tar.gz: 608e3c7a14e1152a712a7071f8cd4ea5fcc0d03b2ed7b3f084b83166cf701b10
5
+ SHA512:
6
+ metadata.gz: 127782ae3c0c6a546af57e851cb3c70bcf65c222b489066cd85167618dca05eb978988624e3e374c0c765b648b906bcc3be5a0949914cc849b4b1f7a47737682
7
+ data.tar.gz: 647131ea6bc0ff9c341b2156ee5cd102009a83718991b92397aa074f17350b9422630069bc80f01b8b03e32f20c0416a657b9bb6d7d24be6ac0034f00b2b2301
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75', require: false
9
+ gem 'rubocop-rspec', require: false
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Iverson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # lex-anchoring
2
+
3
+ Decision anchoring and reference point effects for brain-modeled agentic AI.
4
+
5
+ ## What It Does
6
+
7
+ Models the cognitive anchoring bias: initial values exert disproportionate gravitational pull on subsequent estimates. Implements both anchoring (estimates are pulled toward initial values) and prospect theory's reference point framing (outcomes are perceived as gains or losses relative to a reference, with losses weighted 2.25x more than equivalent gains).
8
+
9
+ ## Core Concept: Anchor Pull
10
+
11
+ An anchor exerts a pull on estimates proportional to its strength:
12
+
13
+ ```ruby
14
+ # anchored_estimate = (strength * DEFAULT_ANCHOR_WEIGHT * anchor_value) +
15
+ # ((1 - strength * DEFAULT_ANCHOR_WEIGHT) * raw_estimate)
16
+ ```
17
+
18
+ With default weight 0.6 and full strength, a strong anchor pulls 60% toward itself.
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ client = Legion::Extensions::Anchoring::Client.new
24
+
25
+ # Record an anchor (e.g., initial budget estimate)
26
+ client.record_anchor(value: 100_000.0, domain: :budget)
27
+
28
+ # Evaluate a new estimate — it will be pulled toward the anchor
29
+ result = client.evaluate_estimate(estimate: 150_000.0, domain: :budget)
30
+ # => { anchored_estimate: 130_000.0, pull_strength: 0.6, correction: 20_000.0 }
31
+
32
+ # Frame a value as gain or loss (with 2.25x loss aversion)
33
+ client.reference_frame(value: 80_000.0, domain: :budget)
34
+ # => { gain_or_loss: :loss, magnitude: 45_000.0, reference: 100_000.0 }
35
+
36
+ # Remove the bias to get the corrected estimate
37
+ client.de_anchor(estimate: 130_000.0, domain: :budget)
38
+ # => { corrected_estimate: 110_000.0, anchor_bias: 20_000.0 }
39
+
40
+ # Maintenance
41
+ client.update_anchoring
42
+ ```
43
+
44
+ ## Integration
45
+
46
+ Pairs with lex-bias for comprehensive bias detection. `reference_frame` output feeds naturally into lex-emotion for emotional gain/loss responses. Wire into decision phases where the agent evaluates numerical estimates or compares outcomes against baselines.
47
+
48
+ ## Development
49
+
50
+ ```bash
51
+ bundle install
52
+ bundle exec rspec
53
+ bundle exec rubocop
54
+ ```
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/anchoring/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-anchoring'
7
+ spec.version = Legion::Extensions::Anchoring::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Anchoring'
12
+ spec.description = 'Decision anchoring and reference point effects for brain-modeled agentic AI'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-anchoring'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-anchoring'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-anchoring'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-anchoring'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-anchoring/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ Dir.glob('{lib,spec}/**/*') + %w[lex-anchoring.gemspec Gemfile LICENSE README.md]
26
+ end
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'legion-gaia'
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/anchoring/helpers/constants'
4
+ require 'legion/extensions/anchoring/helpers/anchor'
5
+ require 'legion/extensions/anchoring/helpers/anchor_store'
6
+ require 'legion/extensions/anchoring/runners/anchoring'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Anchoring
11
+ class Client
12
+ include Runners::Anchoring
13
+
14
+ attr_reader :anchor_store
15
+
16
+ def initialize(anchor_store: nil, **)
17
+ @anchor_store = anchor_store || Helpers::AnchorStore.new
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Anchoring
8
+ module Helpers
9
+ class Anchor
10
+ include Constants
11
+
12
+ attr_reader :id, :value, :domain, :strength, :created_at, :last_accessed
13
+
14
+ def initialize(value:, domain: :general)
15
+ @id = SecureRandom.uuid
16
+ @value = value.to_f
17
+ @domain = domain.to_sym
18
+ @strength = 1.0
19
+ @created_at = Time.now.utc
20
+ @last_accessed = Time.now.utc
21
+ end
22
+
23
+ def decay
24
+ @strength = [(@strength - Constants::ANCHOR_DECAY), 0.0].max
25
+ end
26
+
27
+ def reinforce(observed_value:)
28
+ @last_accessed = Time.now.utc
29
+ alpha = Constants::ADJUSTMENT_RATE
30
+ @value = ((1.0 - alpha) * @value) + (alpha * observed_value.to_f)
31
+ @strength = [@strength + 0.1, 1.0].min
32
+ end
33
+
34
+ def pull(estimate:)
35
+ weight = @strength * Constants::DEFAULT_ANCHOR_WEIGHT
36
+ (weight * @value) + ((1.0 - weight) * estimate.to_f)
37
+ end
38
+
39
+ def label
40
+ Constants::ANCHOR_LABELS.each do |range, lbl|
41
+ return lbl if range.cover?(@strength)
42
+ end
43
+ :fading
44
+ end
45
+
46
+ def to_h
47
+ {
48
+ id: @id,
49
+ value: @value,
50
+ domain: @domain,
51
+ strength: @strength,
52
+ label: label,
53
+ created_at: @created_at,
54
+ last_accessed: @last_accessed
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Anchoring
6
+ module Helpers
7
+ class AnchorStore
8
+ include Constants
9
+
10
+ def initialize
11
+ @anchors = {} # domain (Symbol) -> Array<Anchor>
12
+ @references = {} # domain (Symbol) -> Float (explicit reference point)
13
+ end
14
+
15
+ def add(value:, domain: :general)
16
+ domain = domain.to_sym
17
+ @anchors[domain] ||= []
18
+
19
+ anchor = Anchor.new(value: value, domain: domain)
20
+ @anchors[domain] << anchor
21
+
22
+ if @anchors[domain].size > Constants::MAX_ANCHORS_PER_DOMAIN
23
+ @anchors[domain] = @anchors[domain].sort_by(&:strength).last(Constants::MAX_ANCHORS_PER_DOMAIN)
24
+ end
25
+
26
+ prune_domains
27
+ anchor
28
+ end
29
+
30
+ def strongest(domain: :general)
31
+ domain = domain.to_sym
32
+ anchors = @anchors[domain]
33
+ return nil if anchors.nil? || anchors.empty?
34
+
35
+ anchors.max_by(&:strength)
36
+ end
37
+
38
+ def find(id:)
39
+ @anchors.each_value do |arr|
40
+ arr.each { |a| return a if a.id == id }
41
+ end
42
+ nil
43
+ end
44
+
45
+ def evaluate(estimate:, domain: :general)
46
+ domain = domain.to_sym
47
+ anchor = strongest(domain: domain)
48
+ return { anchored_estimate: estimate.to_f, pull_strength: 0.0, anchor_value: nil, correction: 0.0 } if anchor.nil?
49
+
50
+ anchored = anchor.pull(estimate: estimate)
51
+ pull_str = anchor.strength * Constants::DEFAULT_ANCHOR_WEIGHT
52
+ correction = estimate.to_f - anchored
53
+
54
+ anchor.reinforce(observed_value: estimate)
55
+
56
+ {
57
+ anchored_estimate: anchored,
58
+ pull_strength: pull_str,
59
+ anchor_value: anchor.value,
60
+ correction: correction
61
+ }
62
+ end
63
+
64
+ def reference_frame(value:, domain: :general)
65
+ domain = domain.to_sym
66
+ reference = @references[domain] || strongest(domain: domain)&.value
67
+ return { perceived_value: value.to_f, gain_or_loss: :neutral, magnitude: 0.0 } if reference.nil?
68
+
69
+ diff = value.to_f - reference
70
+ gain_loss = diff >= 0 ? :gain : :loss
71
+ raw_mag = diff.abs
72
+ magnitude = gain_loss == :loss ? raw_mag * Constants::LOSS_AVERSION_FACTOR : raw_mag
73
+
74
+ { perceived_value: value.to_f, gain_or_loss: gain_loss, magnitude: magnitude, reference: reference, raw_diff: diff }
75
+ end
76
+
77
+ def decay_all
78
+ pruned = 0
79
+ @anchors.each_value do |arr|
80
+ arr.each(&:decay)
81
+ before = arr.size
82
+ arr.reject! { |a| a.strength < Constants::ANCHOR_FLOOR }
83
+ pruned += (before - arr.size)
84
+ end
85
+ @anchors.reject! { |_, arr| arr.empty? }
86
+ pruned
87
+ end
88
+
89
+ def shift_reference(domain:, new_reference:)
90
+ domain = domain.to_sym
91
+ old = @references[domain]
92
+ @references[domain] = new_reference.to_f
93
+
94
+ diff = (new_reference.to_f - old.to_f).abs
95
+ significant = diff >= Constants::REFERENCE_SHIFT_THRESHOLD
96
+
97
+ { domain: domain, old_reference: old, new_reference: new_reference.to_f, significant: significant }
98
+ end
99
+
100
+ def domains
101
+ @anchors.keys.select { |d| @anchors[d]&.any? }
102
+ end
103
+
104
+ def to_h
105
+ total_anchors = @anchors.values.flatten.size
106
+ domain_count = domains.size
107
+
108
+ {
109
+ total_anchors: total_anchors,
110
+ domain_count: domain_count,
111
+ domains: domains,
112
+ references: @references.dup
113
+ }
114
+ end
115
+
116
+ private
117
+
118
+ def prune_domains
119
+ return unless @anchors.size > Constants::MAX_DOMAINS
120
+
121
+ oldest_domain = @anchors.min_by { |_, arr| arr.map(&:last_accessed).min }&.first
122
+ @anchors.delete(oldest_domain) if oldest_domain
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Anchoring
6
+ module Helpers
7
+ module Constants
8
+ DEFAULT_ANCHOR_WEIGHT = 0.6 # How much the anchor pulls toward itself
9
+ ADJUSTMENT_RATE = 0.1 # EMA alpha for updating anchors
10
+ ANCHOR_DECAY = 0.02 # Per-tick decay of anchor strength
11
+ ANCHOR_FLOOR = 0.05 # Minimum anchor strength before pruning
12
+ MAX_ANCHORS_PER_DOMAIN = 20 # Cap per domain
13
+ MAX_DOMAINS = 50 # Cap total domains
14
+ REFERENCE_SHIFT_THRESHOLD = 0.3 # Gap needed to shift reference point
15
+ LOSS_AVERSION_FACTOR = 2.25 # Losses weighted 2.25x vs gains (prospect theory)
16
+
17
+ ANCHOR_LABELS = {
18
+ (0.8..) => :strong,
19
+ (0.5...0.8) => :moderate,
20
+ (0.2...0.5) => :weak,
21
+ (..0.2) => :fading
22
+ }.freeze
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Anchoring
6
+ module Runners
7
+ module Anchoring
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def record_anchor(value:, domain: :general, **)
12
+ anchor = anchor_store.add(value: value, domain: domain)
13
+ Legion::Logging.debug "[anchoring] record_anchor domain=#{domain} value=#{value} id=#{anchor.id}"
14
+ { success: true, anchor: anchor.to_h }
15
+ end
16
+
17
+ def evaluate_estimate(estimate:, domain: :general, **)
18
+ result = anchor_store.evaluate(estimate: estimate, domain: domain)
19
+ Legion::Logging.debug "[anchoring] evaluate_estimate domain=#{domain} estimate=#{estimate} " \
20
+ "anchored=#{result[:anchored_estimate].round(4)} pull=#{result[:pull_strength].round(4)}"
21
+ { success: true }.merge(result)
22
+ end
23
+
24
+ def reference_frame(value:, domain: :general, **)
25
+ result = anchor_store.reference_frame(value: value, domain: domain)
26
+ Legion::Logging.debug "[anchoring] reference_frame domain=#{domain} value=#{value} " \
27
+ "gain_or_loss=#{result[:gain_or_loss]}"
28
+ { success: true }.merge(result)
29
+ end
30
+
31
+ def de_anchor(estimate:, domain: :general, **)
32
+ anchor = anchor_store.strongest(domain: domain)
33
+ if anchor.nil?
34
+ Legion::Logging.debug "[anchoring] de_anchor domain=#{domain} no anchor found"
35
+ return { success: true, corrected_estimate: estimate.to_f, anchor_bias: 0.0, domain: domain }
36
+ end
37
+
38
+ biased = anchor.pull(estimate: estimate)
39
+ bias = biased - estimate.to_f
40
+ corrected = estimate.to_f - bias
41
+
42
+ Legion::Logging.debug "[anchoring] de_anchor domain=#{domain} estimate=#{estimate} " \
43
+ "corrected=#{corrected.round(4)} bias=#{bias.round(4)}"
44
+
45
+ {
46
+ success: true,
47
+ corrected_estimate: corrected,
48
+ original_estimate: estimate.to_f,
49
+ anchor_bias: bias,
50
+ anchor_value: anchor.value,
51
+ domain: domain
52
+ }
53
+ end
54
+
55
+ def shift_reference(domain:, new_reference:, **)
56
+ result = anchor_store.shift_reference(domain: domain, new_reference: new_reference)
57
+ Legion::Logging.info "[anchoring] shift_reference domain=#{domain} new=#{new_reference} significant=#{result[:significant]}"
58
+ { success: true }.merge(result)
59
+ end
60
+
61
+ def update_anchoring(**)
62
+ pruned = anchor_store.decay_all
63
+ Legion::Logging.debug "[anchoring] update_anchoring pruned=#{pruned}"
64
+ { success: true, pruned: pruned }
65
+ end
66
+
67
+ def domain_anchors(domain:, **)
68
+ domain = domain.to_sym
69
+ anchor = anchor_store.strongest(domain: domain)
70
+ all_list = anchor_store.instance_variable_get(:@anchors)[domain] || []
71
+ Legion::Logging.debug "[anchoring] domain_anchors domain=#{domain} count=#{all_list.size}"
72
+ {
73
+ success: true,
74
+ domain: domain,
75
+ count: all_list.size,
76
+ strongest: anchor&.to_h,
77
+ anchors: all_list.map(&:to_h)
78
+ }
79
+ end
80
+
81
+ def anchoring_stats(**)
82
+ stats = anchor_store.to_h
83
+ Legion::Logging.debug "[anchoring] stats domains=#{stats[:domain_count]} total=#{stats[:total_anchors]}"
84
+ { success: true }.merge(stats)
85
+ end
86
+
87
+ private
88
+
89
+ def anchor_store
90
+ @anchor_store ||= Helpers::AnchorStore.new
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Anchoring
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/anchoring/version'
4
+ require 'legion/extensions/anchoring/helpers/constants'
5
+ require 'legion/extensions/anchoring/helpers/anchor'
6
+ require 'legion/extensions/anchoring/helpers/anchor_store'
7
+ require 'legion/extensions/anchoring/runners/anchoring'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Anchoring
12
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/anchoring/client'
4
+
5
+ RSpec.describe Legion::Extensions::Anchoring::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to all runner methods' do
9
+ expect(client).to respond_to(:record_anchor)
10
+ expect(client).to respond_to(:evaluate_estimate)
11
+ expect(client).to respond_to(:reference_frame)
12
+ expect(client).to respond_to(:de_anchor)
13
+ expect(client).to respond_to(:shift_reference)
14
+ expect(client).to respond_to(:update_anchoring)
15
+ expect(client).to respond_to(:domain_anchors)
16
+ expect(client).to respond_to(:anchoring_stats)
17
+ end
18
+
19
+ it 'exposes anchor_store' do
20
+ expect(client.anchor_store).to be_a(Legion::Extensions::Anchoring::Helpers::AnchorStore)
21
+ end
22
+
23
+ it 'accepts an external anchor_store' do
24
+ custom_store = Legion::Extensions::Anchoring::Helpers::AnchorStore.new
25
+ c = described_class.new(anchor_store: custom_store)
26
+ expect(c.anchor_store).to be(custom_store)
27
+ end
28
+
29
+ it 'accepts keyword splat arguments' do
30
+ expect { described_class.new(extra: :ignored) }.not_to raise_error
31
+ end
32
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Anchoring::Helpers::Anchor do
4
+ subject(:anchor) { described_class.new(value: 100.0, domain: :financial) }
5
+
6
+ describe '#initialize' do
7
+ it 'sets value as float' do
8
+ expect(anchor.value).to eq(100.0)
9
+ end
10
+
11
+ it 'sets domain as symbol' do
12
+ expect(anchor.domain).to eq(:financial)
13
+ end
14
+
15
+ it 'sets initial strength to 1.0' do
16
+ expect(anchor.strength).to eq(1.0)
17
+ end
18
+
19
+ it 'generates a uuid id' do
20
+ expect(anchor.id).to match(/\A[0-9a-f-]{36}\z/)
21
+ end
22
+
23
+ it 'sets created_at' do
24
+ expect(anchor.created_at).to be_a(Time)
25
+ end
26
+
27
+ it 'sets last_accessed' do
28
+ expect(anchor.last_accessed).to be_a(Time)
29
+ end
30
+
31
+ it 'accepts integer value and converts to float' do
32
+ a = described_class.new(value: 50, domain: :general)
33
+ expect(a.value).to eq(50.0)
34
+ end
35
+
36
+ it 'defaults domain to :general when not specified' do
37
+ a = described_class.new(value: 10.0)
38
+ expect(a.domain).to eq(:general)
39
+ end
40
+ end
41
+
42
+ describe '#decay' do
43
+ it 'reduces strength by ANCHOR_DECAY' do
44
+ before = anchor.strength
45
+ anchor.decay
46
+ expect(anchor.strength).to be_within(0.001).of(before - Legion::Extensions::Anchoring::Helpers::Constants::ANCHOR_DECAY)
47
+ end
48
+
49
+ it 'does not go below 0.0' do
50
+ 50.times { anchor.decay }
51
+ expect(anchor.strength).to eq(0.0)
52
+ end
53
+ end
54
+
55
+ describe '#reinforce' do
56
+ it 'updates the value toward observed_value via EMA' do
57
+ anchor.reinforce(observed_value: 200.0)
58
+ expect(anchor.value).to be > 100.0
59
+ expect(anchor.value).to be < 200.0
60
+ end
61
+
62
+ it 'bumps strength up (capped at 1.0)' do
63
+ anchor.decay
64
+ before = anchor.strength
65
+ anchor.reinforce(observed_value: 100.0)
66
+ expect(anchor.strength).to be > before
67
+ end
68
+
69
+ it 'updates last_accessed' do
70
+ before = anchor.last_accessed
71
+ sleep 0.001
72
+ anchor.reinforce(observed_value: 100.0)
73
+ expect(anchor.last_accessed).to be >= before
74
+ end
75
+ end
76
+
77
+ describe '#pull' do
78
+ it 'returns a value between anchor value and estimate' do
79
+ result = anchor.pull(estimate: 200.0)
80
+ expect(result).to be > 100.0
81
+ expect(result).to be < 200.0
82
+ end
83
+
84
+ it 'pulls estimate closer to anchor value' do
85
+ biased = anchor.pull(estimate: 200.0)
86
+ expect((biased - 100.0).abs).to be < (200.0 - 100.0).abs
87
+ end
88
+
89
+ it 'returns anchor value when estimate equals anchor value' do
90
+ result = anchor.pull(estimate: 100.0)
91
+ expect(result).to be_within(0.001).of(100.0)
92
+ end
93
+ end
94
+
95
+ describe '#label' do
96
+ it 'returns :strong at full strength' do
97
+ expect(anchor.label).to eq(:strong)
98
+ end
99
+
100
+ it 'returns :moderate at moderate strength' do
101
+ anchor.instance_variable_set(:@strength, 0.6)
102
+ expect(anchor.label).to eq(:moderate)
103
+ end
104
+
105
+ it 'returns :weak at weak strength' do
106
+ anchor.instance_variable_set(:@strength, 0.35)
107
+ expect(anchor.label).to eq(:weak)
108
+ end
109
+
110
+ it 'returns :fading at very low strength' do
111
+ anchor.instance_variable_set(:@strength, 0.1)
112
+ expect(anchor.label).to eq(:fading)
113
+ end
114
+ end
115
+
116
+ describe '#to_h' do
117
+ it 'returns a hash with all expected keys' do
118
+ h = anchor.to_h
119
+ expect(h).to include(:id, :value, :domain, :strength, :label, :created_at, :last_accessed)
120
+ end
121
+
122
+ it 'includes correct value' do
123
+ expect(anchor.to_h[:value]).to eq(100.0)
124
+ end
125
+
126
+ it 'includes label' do
127
+ expect(anchor.to_h[:label]).to eq(:strong)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Anchoring::Helpers::AnchorStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ describe '#add' do
7
+ it 'creates and returns an Anchor' do
8
+ anchor = store.add(value: 50.0, domain: :financial)
9
+ expect(anchor).to be_a(Legion::Extensions::Anchoring::Helpers::Anchor)
10
+ end
11
+
12
+ it 'stores anchors per domain' do
13
+ store.add(value: 50.0, domain: :financial)
14
+ store.add(value: 80.0, domain: :financial)
15
+ store.add(value: 10.0, domain: :temporal)
16
+ expect(store.domains).to include(:financial, :temporal)
17
+ end
18
+
19
+ it 'enforces MAX_ANCHORS_PER_DOMAIN limit' do
20
+ max = Legion::Extensions::Anchoring::Helpers::Constants::MAX_ANCHORS_PER_DOMAIN
21
+ (max + 5).times { |i| store.add(value: i.to_f, domain: :financial) }
22
+ all = store.instance_variable_get(:@anchors)[:financial]
23
+ expect(all.size).to be <= max
24
+ end
25
+
26
+ it 'accepts integer value' do
27
+ anchor = store.add(value: 100, domain: :general)
28
+ expect(anchor.value).to eq(100.0)
29
+ end
30
+ end
31
+
32
+ describe '#strongest' do
33
+ it 'returns nil for unknown domain' do
34
+ expect(store.strongest(domain: :unknown)).to be_nil
35
+ end
36
+
37
+ it 'returns the anchor with highest strength' do
38
+ a1 = store.add(value: 10.0, domain: :financial)
39
+ a2 = store.add(value: 20.0, domain: :financial)
40
+ a1.instance_variable_set(:@strength, 0.3)
41
+ a2.instance_variable_set(:@strength, 0.9)
42
+ expect(store.strongest(domain: :financial).value).to eq(20.0)
43
+ end
44
+ end
45
+
46
+ describe '#find' do
47
+ it 'finds anchor by id' do
48
+ anchor = store.add(value: 42.0, domain: :general)
49
+ found = store.find(id: anchor.id)
50
+ expect(found).to eq(anchor)
51
+ end
52
+
53
+ it 'returns nil for unknown id' do
54
+ expect(store.find(id: 'nonexistent')).to be_nil
55
+ end
56
+ end
57
+
58
+ describe '#evaluate' do
59
+ it 'returns no-pull result when domain has no anchors' do
60
+ result = store.evaluate(estimate: 100.0, domain: :empty)
61
+ expect(result[:pull_strength]).to eq(0.0)
62
+ expect(result[:anchored_estimate]).to eq(100.0)
63
+ expect(result[:anchor_value]).to be_nil
64
+ end
65
+
66
+ it 'returns biased estimate when anchor exists' do
67
+ store.add(value: 50.0, domain: :financial)
68
+ result = store.evaluate(estimate: 100.0, domain: :financial)
69
+ expect(result[:anchored_estimate]).to be > 50.0
70
+ expect(result[:anchored_estimate]).to be < 100.0
71
+ end
72
+
73
+ it 'includes pull_strength, anchor_value, correction keys' do
74
+ store.add(value: 50.0, domain: :financial)
75
+ result = store.evaluate(estimate: 100.0, domain: :financial)
76
+ expect(result).to include(:pull_strength, :anchor_value, :correction)
77
+ end
78
+
79
+ it 'correction is estimate minus anchored_estimate' do
80
+ store.add(value: 50.0, domain: :financial)
81
+ result = store.evaluate(estimate: 100.0, domain: :financial)
82
+ expect(result[:correction]).to be_within(0.001).of(100.0 - result[:anchored_estimate])
83
+ end
84
+ end
85
+
86
+ describe '#reference_frame' do
87
+ it 'returns neutral when no reference or anchor' do
88
+ result = store.reference_frame(value: 100.0, domain: :empty)
89
+ expect(result[:gain_or_loss]).to eq(:neutral)
90
+ expect(result[:magnitude]).to eq(0.0)
91
+ end
92
+
93
+ it 'detects gain when value above reference' do
94
+ store.shift_reference(domain: :financial, new_reference: 50.0)
95
+ result = store.reference_frame(value: 100.0, domain: :financial)
96
+ expect(result[:gain_or_loss]).to eq(:gain)
97
+ expect(result[:magnitude]).to be > 0
98
+ end
99
+
100
+ it 'detects loss when value below reference' do
101
+ store.shift_reference(domain: :financial, new_reference: 100.0)
102
+ result = store.reference_frame(value: 50.0, domain: :financial)
103
+ expect(result[:gain_or_loss]).to eq(:loss)
104
+ end
105
+
106
+ it 'applies loss aversion factor to losses' do
107
+ store.shift_reference(domain: :financial, new_reference: 100.0)
108
+ result = store.reference_frame(value: 50.0, domain: :financial)
109
+ raw_diff = 50.0
110
+ expected_magnitude = raw_diff * Legion::Extensions::Anchoring::Helpers::Constants::LOSS_AVERSION_FACTOR
111
+ expect(result[:magnitude]).to be_within(0.001).of(expected_magnitude)
112
+ end
113
+
114
+ it 'does not apply loss aversion to gains' do
115
+ store.shift_reference(domain: :financial, new_reference: 50.0)
116
+ result = store.reference_frame(value: 100.0, domain: :financial)
117
+ expect(result[:magnitude]).to be_within(0.001).of(50.0)
118
+ end
119
+
120
+ it 'uses anchor as implicit reference when no explicit reference set' do
121
+ store.add(value: 50.0, domain: :financial)
122
+ result = store.reference_frame(value: 100.0, domain: :financial)
123
+ expect(result[:gain_or_loss]).to eq(:gain)
124
+ end
125
+ end
126
+
127
+ describe '#decay_all' do
128
+ it 'decays all anchors and returns pruned count' do
129
+ store.add(value: 10.0, domain: :financial)
130
+ pruned = store.decay_all
131
+ expect(pruned).to be >= 0
132
+ end
133
+
134
+ it 'prunes anchors that fall below ANCHOR_FLOOR' do
135
+ anchor = store.add(value: 10.0, domain: :financial)
136
+ anchor.instance_variable_set(:@strength, Legion::Extensions::Anchoring::Helpers::Constants::ANCHOR_FLOOR - 0.001)
137
+ store.decay_all
138
+ expect(store.domains).not_to include(:financial)
139
+ end
140
+
141
+ it 'retains anchors above floor' do
142
+ store.add(value: 10.0, domain: :financial)
143
+ store.decay_all
144
+ all = store.instance_variable_get(:@anchors)[:financial]
145
+ expect(all).not_to be_nil
146
+ end
147
+ end
148
+
149
+ describe '#shift_reference' do
150
+ it 'sets new reference point for domain' do
151
+ store.shift_reference(domain: :financial, new_reference: 100.0)
152
+ refs = store.instance_variable_get(:@references)
153
+ expect(refs[:financial]).to eq(100.0)
154
+ end
155
+
156
+ it 'returns old and new reference' do
157
+ store.shift_reference(domain: :financial, new_reference: 50.0)
158
+ result = store.shift_reference(domain: :financial, new_reference: 100.0)
159
+ expect(result[:old_reference]).to eq(50.0)
160
+ expect(result[:new_reference]).to eq(100.0)
161
+ end
162
+
163
+ it 'marks shift as significant when diff >= REFERENCE_SHIFT_THRESHOLD' do
164
+ store.shift_reference(domain: :temporal, new_reference: 0.0)
165
+ result = store.shift_reference(domain: :temporal, new_reference: 1.0)
166
+ expect(result[:significant]).to be true
167
+ end
168
+
169
+ it 'marks shift as not significant when diff < REFERENCE_SHIFT_THRESHOLD' do
170
+ store.shift_reference(domain: :temporal, new_reference: 0.0)
171
+ result = store.shift_reference(domain: :temporal, new_reference: 0.1)
172
+ expect(result[:significant]).to be false
173
+ end
174
+ end
175
+
176
+ describe '#domains' do
177
+ it 'returns empty array when no anchors' do
178
+ expect(store.domains).to be_empty
179
+ end
180
+
181
+ it 'returns list of active domains' do
182
+ store.add(value: 1.0, domain: :financial)
183
+ store.add(value: 2.0, domain: :temporal)
184
+ expect(store.domains).to match_array(%i[financial temporal])
185
+ end
186
+ end
187
+
188
+ describe '#to_h' do
189
+ it 'returns summary hash' do
190
+ store.add(value: 10.0, domain: :financial)
191
+ h = store.to_h
192
+ expect(h).to include(:total_anchors, :domain_count, :domains, :references)
193
+ end
194
+
195
+ it 'reflects correct total_anchors count' do
196
+ store.add(value: 10.0, domain: :financial)
197
+ store.add(value: 20.0, domain: :financial)
198
+ expect(store.to_h[:total_anchors]).to eq(2)
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Anchoring::Helpers::Constants do
4
+ it 'defines DEFAULT_ANCHOR_WEIGHT as 0.6' do
5
+ expect(described_module::DEFAULT_ANCHOR_WEIGHT).to eq(0.6)
6
+ end
7
+
8
+ it 'defines ADJUSTMENT_RATE as 0.1' do
9
+ expect(described_module::ADJUSTMENT_RATE).to eq(0.1)
10
+ end
11
+
12
+ it 'defines ANCHOR_DECAY as 0.02' do
13
+ expect(described_module::ANCHOR_DECAY).to eq(0.02)
14
+ end
15
+
16
+ it 'defines ANCHOR_FLOOR as 0.05' do
17
+ expect(described_module::ANCHOR_FLOOR).to eq(0.05)
18
+ end
19
+
20
+ it 'defines MAX_ANCHORS_PER_DOMAIN as 20' do
21
+ expect(described_module::MAX_ANCHORS_PER_DOMAIN).to eq(20)
22
+ end
23
+
24
+ it 'defines MAX_DOMAINS as 50' do
25
+ expect(described_module::MAX_DOMAINS).to eq(50)
26
+ end
27
+
28
+ it 'defines REFERENCE_SHIFT_THRESHOLD as 0.3' do
29
+ expect(described_module::REFERENCE_SHIFT_THRESHOLD).to eq(0.3)
30
+ end
31
+
32
+ it 'defines LOSS_AVERSION_FACTOR as 2.25' do
33
+ expect(described_module::LOSS_AVERSION_FACTOR).to eq(2.25)
34
+ end
35
+
36
+ it 'defines ANCHOR_LABELS with 4 entries' do
37
+ expect(described_module::ANCHOR_LABELS.size).to eq(4)
38
+ end
39
+
40
+ it 'ANCHOR_LABELS maps high strength to :strong' do
41
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.9) }&.last
42
+ expect(label).to eq(:strong)
43
+ end
44
+
45
+ it 'ANCHOR_LABELS maps mid strength to :moderate' do
46
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.6) }&.last
47
+ expect(label).to eq(:moderate)
48
+ end
49
+
50
+ it 'ANCHOR_LABELS maps low strength to :weak' do
51
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.3) }&.last
52
+ expect(label).to eq(:weak)
53
+ end
54
+
55
+ it 'ANCHOR_LABELS maps very low strength to :fading' do
56
+ label = described_module::ANCHOR_LABELS.find { |range, _| range.cover?(0.1) }&.last
57
+ expect(label).to eq(:fading)
58
+ end
59
+
60
+ def described_module
61
+ Legion::Extensions::Anchoring::Helpers::Constants
62
+ end
63
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/anchoring/client'
4
+
5
+ RSpec.describe Legion::Extensions::Anchoring::Runners::Anchoring do
6
+ let(:client) { Legion::Extensions::Anchoring::Client.new }
7
+
8
+ describe '#record_anchor' do
9
+ it 'returns success: true' do
10
+ result = client.record_anchor(value: 100.0, domain: :financial)
11
+ expect(result[:success]).to be true
12
+ end
13
+
14
+ it 'returns anchor hash' do
15
+ result = client.record_anchor(value: 100.0, domain: :financial)
16
+ expect(result[:anchor]).to include(:id, :value, :domain, :strength)
17
+ end
18
+
19
+ it 'uses :general domain by default' do
20
+ result = client.record_anchor(value: 50.0)
21
+ expect(result[:anchor][:domain]).to eq(:general)
22
+ end
23
+
24
+ it 'accepts keyword splat' do
25
+ expect { client.record_anchor(value: 10.0, extra: :ignored) }.not_to raise_error
26
+ end
27
+ end
28
+
29
+ describe '#evaluate_estimate' do
30
+ it 'returns success: true' do
31
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
32
+ expect(result[:success]).to be true
33
+ end
34
+
35
+ it 'returns no-pull when domain has no anchors' do
36
+ result = client.evaluate_estimate(estimate: 100.0, domain: :noanchor)
37
+ expect(result[:pull_strength]).to eq(0.0)
38
+ expect(result[:anchored_estimate]).to eq(100.0)
39
+ end
40
+
41
+ it 'biases estimate toward anchor when anchor exists' do
42
+ client.record_anchor(value: 50.0, domain: :financial)
43
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
44
+ expect(result[:anchored_estimate]).to be < 100.0
45
+ expect(result[:anchored_estimate]).to be > 50.0
46
+ end
47
+
48
+ it 'includes pull_strength, anchor_value, correction' do
49
+ client.record_anchor(value: 50.0, domain: :financial)
50
+ result = client.evaluate_estimate(estimate: 100.0, domain: :financial)
51
+ expect(result).to include(:pull_strength, :anchor_value, :correction)
52
+ end
53
+ end
54
+
55
+ describe '#reference_frame' do
56
+ it 'returns success: true' do
57
+ result = client.reference_frame(value: 100.0, domain: :financial)
58
+ expect(result[:success]).to be true
59
+ end
60
+
61
+ it 'returns neutral for domain without reference' do
62
+ result = client.reference_frame(value: 100.0, domain: :empty_domain)
63
+ expect(result[:gain_or_loss]).to eq(:neutral)
64
+ end
65
+
66
+ it 'detects gain after setting reference' do
67
+ client.shift_reference(domain: :financial, new_reference: 50.0)
68
+ result = client.reference_frame(value: 100.0, domain: :financial)
69
+ expect(result[:gain_or_loss]).to eq(:gain)
70
+ end
71
+
72
+ it 'detects loss after setting reference' do
73
+ client.shift_reference(domain: :financial, new_reference: 100.0)
74
+ result = client.reference_frame(value: 50.0, domain: :financial)
75
+ expect(result[:gain_or_loss]).to eq(:loss)
76
+ end
77
+
78
+ it 'applies loss aversion factor to losses' do
79
+ client.shift_reference(domain: :financial, new_reference: 100.0)
80
+ result = client.reference_frame(value: 50.0, domain: :financial)
81
+ expect(result[:magnitude]).to be_within(0.001).of(50.0 * 2.25)
82
+ end
83
+ end
84
+
85
+ describe '#de_anchor' do
86
+ it 'returns success: true' do
87
+ result = client.de_anchor(estimate: 100.0, domain: :empty_de)
88
+ expect(result[:success]).to be true
89
+ end
90
+
91
+ it 'returns original estimate with zero bias when no anchor' do
92
+ result = client.de_anchor(estimate: 100.0, domain: :no_anchor_domain)
93
+ expect(result[:corrected_estimate]).to eq(100.0)
94
+ expect(result[:anchor_bias]).to eq(0.0)
95
+ end
96
+
97
+ it 'returns corrected estimate that removes anchor bias' do
98
+ client.record_anchor(value: 50.0, domain: :financial)
99
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
100
+ expect(result[:corrected_estimate]).to be > 100.0
101
+ end
102
+
103
+ it 'returns anchor_value when anchor present' do
104
+ client.record_anchor(value: 50.0, domain: :financial)
105
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
106
+ expect(result[:anchor_value]).to eq(50.0)
107
+ end
108
+
109
+ it 'includes original_estimate' do
110
+ client.record_anchor(value: 50.0, domain: :financial)
111
+ result = client.de_anchor(estimate: 100.0, domain: :financial)
112
+ expect(result[:original_estimate]).to eq(100.0)
113
+ end
114
+ end
115
+
116
+ describe '#shift_reference' do
117
+ it 'returns success: true' do
118
+ result = client.shift_reference(domain: :financial, new_reference: 100.0)
119
+ expect(result[:success]).to be true
120
+ end
121
+
122
+ it 'includes domain, old_reference, new_reference, significant' do
123
+ result = client.shift_reference(domain: :financial, new_reference: 100.0)
124
+ expect(result).to include(:domain, :old_reference, :new_reference, :significant)
125
+ end
126
+
127
+ it 'marks large shift as significant' do
128
+ client.shift_reference(domain: :financial, new_reference: 0.0)
129
+ result = client.shift_reference(domain: :financial, new_reference: 1.0)
130
+ expect(result[:significant]).to be true
131
+ end
132
+ end
133
+
134
+ describe '#update_anchoring' do
135
+ it 'returns success: true' do
136
+ result = client.update_anchoring
137
+ expect(result[:success]).to be true
138
+ end
139
+
140
+ it 'returns pruned count' do
141
+ result = client.update_anchoring
142
+ expect(result).to have_key(:pruned)
143
+ end
144
+
145
+ it 'decays existing anchors' do
146
+ client.record_anchor(value: 100.0, domain: :financial)
147
+ anchor = client.anchor_store.strongest(domain: :financial)
148
+ before_strength = anchor.strength
149
+ client.update_anchoring
150
+ expect(anchor.strength).to be < before_strength
151
+ end
152
+ end
153
+
154
+ describe '#domain_anchors' do
155
+ it 'returns success: true' do
156
+ result = client.domain_anchors(domain: :financial)
157
+ expect(result[:success]).to be true
158
+ end
159
+
160
+ it 'returns empty anchors for unknown domain' do
161
+ result = client.domain_anchors(domain: :no_such_domain)
162
+ expect(result[:count]).to eq(0)
163
+ expect(result[:anchors]).to eq([])
164
+ end
165
+
166
+ it 'returns anchors for known domain' do
167
+ client.record_anchor(value: 100.0, domain: :financial)
168
+ result = client.domain_anchors(domain: :financial)
169
+ expect(result[:count]).to eq(1)
170
+ expect(result[:anchors].first[:value]).to eq(100.0)
171
+ end
172
+
173
+ it 'includes strongest anchor' do
174
+ client.record_anchor(value: 100.0, domain: :financial)
175
+ result = client.domain_anchors(domain: :financial)
176
+ expect(result[:strongest]).not_to be_nil
177
+ end
178
+ end
179
+
180
+ describe '#anchoring_stats' do
181
+ it 'returns success: true' do
182
+ result = client.anchoring_stats
183
+ expect(result[:success]).to be true
184
+ end
185
+
186
+ it 'includes total_anchors, domain_count, domains' do
187
+ result = client.anchoring_stats
188
+ expect(result).to include(:total_anchors, :domain_count, :domains)
189
+ end
190
+
191
+ it 'reflects anchor counts' do
192
+ client.record_anchor(value: 10.0, domain: :financial)
193
+ client.record_anchor(value: 20.0, domain: :temporal)
194
+ result = client.anchoring_stats
195
+ expect(result[:total_anchors]).to eq(2)
196
+ expect(result[:domain_count]).to eq(2)
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ module Legion
6
+ module Logging
7
+ def self.debug(_msg); end
8
+ def self.info(_msg); end
9
+ def self.warn(_msg); end
10
+ def self.error(_msg); end
11
+ end
12
+
13
+ module Extensions
14
+ module Helpers
15
+ module Lex; end
16
+ end
17
+ end
18
+ end
19
+
20
+ require 'legion/extensions/anchoring'
21
+ require 'legion/extensions/anchoring/client'
22
+
23
+ RSpec.configure do |config|
24
+ config.example_status_persistence_file_path = '.rspec_status'
25
+ config.disable_monkey_patching!
26
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
27
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-anchoring
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: legion-gaia
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: Decision anchoring and reference point effects for brain-modeled agentic
27
+ AI
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - LICENSE
36
+ - README.md
37
+ - lex-anchoring.gemspec
38
+ - lib/legion/extensions/anchoring.rb
39
+ - lib/legion/extensions/anchoring/client.rb
40
+ - lib/legion/extensions/anchoring/helpers/anchor.rb
41
+ - lib/legion/extensions/anchoring/helpers/anchor_store.rb
42
+ - lib/legion/extensions/anchoring/helpers/constants.rb
43
+ - lib/legion/extensions/anchoring/runners/anchoring.rb
44
+ - lib/legion/extensions/anchoring/version.rb
45
+ - spec/legion/extensions/anchoring/client_spec.rb
46
+ - spec/legion/extensions/anchoring/helpers/anchor_spec.rb
47
+ - spec/legion/extensions/anchoring/helpers/anchor_store_spec.rb
48
+ - spec/legion/extensions/anchoring/helpers/constants_spec.rb
49
+ - spec/legion/extensions/anchoring/runners/anchoring_spec.rb
50
+ - spec/spec_helper.rb
51
+ homepage: https://github.com/LegionIO/lex-anchoring
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/LegionIO/lex-anchoring
56
+ source_code_uri: https://github.com/LegionIO/lex-anchoring
57
+ documentation_uri: https://github.com/LegionIO/lex-anchoring
58
+ changelog_uri: https://github.com/LegionIO/lex-anchoring
59
+ bug_tracker_uri: https://github.com/LegionIO/lex-anchoring/issues
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: LEX Anchoring
78
+ test_files: []