lex-cognitive-synesthesia 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: f5b3932afe6397f2043ffad49ca9d50f526274cf5f6fa1a14b9ee7089570e3f9
4
+ data.tar.gz: 3498493819add151ac7d8432da16511f09d01b1b762999f75c4f7b10471a0407
5
+ SHA512:
6
+ metadata.gz: 3c7a1a08186b1fed2821e1c275b1b3b50ddb63e0b0efeb9a26c95046e8d3b3e36020d628eec2edac547701a645e89d4dee9989005fd9381c4caa4eb491943d02
7
+ data.tar.gz: 69a277b2cd7741a4bfc07bb6aed1d10ed278fe6681daac47bd5aed452010ec55c476ae690d6a8c8f280c32868449f754dd698bab59141f93f4c4fcad77b880bc
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.gem
10
+ .rspec_status
11
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,64 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.4
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Layout/LineLength:
7
+ Max: 160
8
+
9
+ Layout/SpaceAroundEqualsInParameterDefault:
10
+ EnforcedStyle: space
11
+
12
+ Layout/HashAlignment:
13
+ EnforcedHashRocketStyle: table
14
+ EnforcedColonStyle: table
15
+
16
+ Metrics/MethodLength:
17
+ Max: 25
18
+
19
+ Metrics/ClassLength:
20
+ Max: 150
21
+
22
+ Metrics/ModuleLength:
23
+ Max: 1500
24
+
25
+ Metrics/BlockLength:
26
+ Max: 40
27
+ Exclude:
28
+ - 'spec/**/*'
29
+
30
+ Metrics/AbcSize:
31
+ Max: 25
32
+
33
+ Metrics/CyclomaticComplexity:
34
+ Max: 15
35
+
36
+ Metrics/PerceivedComplexity:
37
+ Max: 17
38
+
39
+ Metrics/ParameterLists:
40
+ Max: 8
41
+ MaxOptionalParameters: 8
42
+
43
+ Style/Documentation:
44
+ Enabled: false
45
+
46
+ Style/SymbolArray:
47
+ Enabled: true
48
+
49
+ Style/FrozenStringLiteralComment:
50
+ Enabled: true
51
+ EnforcedStyle: always
52
+
53
+ Style/OneClassPerFile:
54
+ Exclude:
55
+ - 'spec/spec_helper.rb'
56
+
57
+ Naming/FileName:
58
+ Enabled: false
59
+
60
+ Naming/PredicateMethod:
61
+ Enabled: false
62
+
63
+ Naming/PredicatePrefix:
64
+ Enabled: false
data/CLAUDE.md ADDED
@@ -0,0 +1,119 @@
1
+ # lex-cognitive-synesthesia
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-cognitive-synesthesia`
6
+ - **Version**: 0.1.0
7
+ - **Namespace**: `Legion::Extensions::CognitiveSynesthesia`
8
+
9
+ ## Purpose
10
+
11
+ Models cross-modal cognitive associations inspired by synesthesia — the neurological phenomenon where stimulation of one sense involuntarily triggers another. Sensory mappings link a source modality to a target modality via trigger and response patterns. When a source modality is activated, all active mappings for it fire, producing `SynestheticEvent` records. Mappings strengthen with use and decay over time. This models how cognitive input in one domain (e.g., emotional) automatically invokes representations in another (e.g., spatial or visual).
12
+
13
+ ## Gem Info
14
+
15
+ - **Gemspec**: `lex-cognitive-synesthesia.gemspec`
16
+ - **Require**: `lex-cognitive-synesthesia`
17
+ - **Ruby**: >= 3.4
18
+ - **License**: MIT
19
+ - **Homepage**: https://github.com/LegionIO/lex-cognitive-synesthesia
20
+
21
+ ## File Structure
22
+
23
+ ```
24
+ lib/legion/extensions/cognitive_synesthesia/
25
+ version.rb
26
+ helpers/
27
+ constants.rb # Modalities, strength/richness/intensity label tables, thresholds
28
+ sensory_mapping.rb # SensoryMapping class — one cross-modal association
29
+ synesthetic_event.rb # SynestheticEvent class — one fired cross-modal experience
30
+ synesthesia_engine.rb # SynesthesiaEngine — manages mappings and event log
31
+ runners/
32
+ cognitive_synesthesia.rb # Runner module — public API
33
+ client.rb
34
+ ```
35
+
36
+ ## Key Constants
37
+
38
+ | Constant | Value | Meaning |
39
+ |---|---|---|
40
+ | `MAX_MAPPINGS` | 200 | Hard cap; weakest mappings pruned when exceeded |
41
+ | `MAX_EVENTS` | 500 | Event log ring size |
42
+ | `DEFAULT_STRENGTH` | 0.5 | Starting strength for new mappings |
43
+ | `STRENGTH_BOOST` | 0.1 | Strength increase per `activate!` call |
44
+ | `STRENGTH_DECAY` | 0.02 | Strength decrease per `decay!` call |
45
+ | `TRIGGER_THRESHOLD` | 0.3 | Minimum strength for a mapping to fire |
46
+
47
+ `MODALITIES`: `[:visual, :auditory, :tactile, :emotional, :semantic, :temporal, :spatial, :abstract]`
48
+
49
+ Strength labels: `0.8+` = `:dominant`, `0.6..0.8` = `:strong`, `0.4..0.6` = `:moderate`, `0.2..0.4` = `:faint`, `<0.2` = `:trace`
50
+
51
+ Richness labels (for `cross_modal_richness`): `0.8+` = `:synesthetic`, `0.6..0.8` = `:vivid`, `0.4..0.6` = `:partial`, `0.2..0.4` = `:sparse`, `<0.2` = `:amodal`
52
+
53
+ Intensity labels (for events): `0.8+` = `:overwhelming`, `0.6..0.8` = `:intense`, `0.4..0.6` = `:moderate`, `0.2..0.4` = `:subtle`, `<0.2` = `:subliminal`
54
+
55
+ ## Key Classes
56
+
57
+ ### `Helpers::SensoryMapping`
58
+
59
+ One cross-modal association with source/target modalities and pattern descriptors.
60
+
61
+ - `activate!` — increments `activation_count`, boosts strength by `STRENGTH_BOOST`
62
+ - `decay!` — reduces strength by `STRENGTH_DECAY`
63
+ - `active?` — strength >= `TRIGGER_THRESHOLD`
64
+ - `strength_label` — one of the `STRENGTH_LABELS` values
65
+ - Fields: `id` (UUID), `source_modality`, `target_modality`, `trigger_pattern` (caller-defined), `response_pattern` (caller-defined), `strength`, `activation_count`, `created_at`, `last_activated_at`
66
+
67
+ ### `Helpers::SynestheticEvent`
68
+
69
+ A single fired cross-modal experience.
70
+
71
+ - `intensity_label` — one of the `INTENSITY_LABELS` values
72
+ - `involuntary` — always true (default); reflects the automatic nature of synesthetic responses
73
+ - `target_output` — hash with `{ modality:, response_pattern:, source_input:, source_modality: }`
74
+ - Fields: `id` (UUID), `mapping_id`, `source_input`, `target_output`, `intensity`, `triggered_at`
75
+
76
+ ### `Helpers::SynesthesiaEngine`
77
+
78
+ Registry and event processor for all mappings and events.
79
+
80
+ - `register_mapping(source_modality:, target_modality:, trigger_pattern:, response_pattern:, strength:)` — validates modalities; prunes weakest mappings if over `MAX_MAPPINGS`
81
+ - `trigger(source_modality:, input:)` — finds all active mappings for source, fires each with `fire_event`, returns event list
82
+ - `decay_mappings!` — decays all mappings; removes those with strength <= 0.0
83
+ - `cross_modal_richness` — active unique pairs / `(8 * 7)` possible pairs; range `[0.0, 1.0]`
84
+ - `dominant_modality_pairs(limit:)` — top pairs by cumulative activation count
85
+ - `event_history(limit:)` — most recent events from ring buffer
86
+ - `modality_coverage` — set of modalities appearing in active mappings
87
+
88
+ ## Runners
89
+
90
+ Module: `Legion::Extensions::CognitiveSynesthesia::Runners::CognitiveSynesthesia`
91
+
92
+ | Runner | Key Args | Returns |
93
+ |---|---|---|
94
+ | `register_mapping` | `source_modality:`, `target_modality:`, `trigger_pattern:`, `response_pattern:`, `strength:` | `{ success:, mapping_id:, source_modality:, target_modality: }` or `{ success: false, error: :invalid_modality }` |
95
+ | `trigger` | `source_modality:`, `input:` | `{ success:, events:, triggered_count: }` |
96
+ | `decay_mappings` | — | `{ success:, mappings_removed:, mappings_remaining: }` |
97
+ | `cross_modal_richness` | — | `{ success:, richness:, richness_label: }` |
98
+ | `dominant_modality_pairs` | `limit:` | `{ success:, pairs:, count: }` |
99
+ | `event_history` | `limit:` | `{ success:, events:, count: }` |
100
+ | `modality_coverage` | — | `{ success:, covered_modalities:, coverage_count:, total_modalities: }` |
101
+ | `synesthesia_report` | — | aggregate report with richness, active count, dominant pairs, coverage |
102
+
103
+ All runners accept optional `engine:` keyword for test injection.
104
+
105
+ ## Integration Points
106
+
107
+ - No actors defined; `decay_mappings` should be called periodically (e.g., from `lex-tick`)
108
+ - `trigger_pattern` and `response_pattern` are caller-defined — can be strings, symbols, or hashes
109
+ - Can represent cross-extension stimulus linkages: e.g., emotional triggers spatial patterns, or temporal triggers semantic associations
110
+ - `cross_modal_richness` measures the breadth of cross-modal connections — a proxy for cognitive integration
111
+ - All state is in-memory per `SynesthesiaEngine` instance
112
+
113
+ ## Development Notes
114
+
115
+ - Both `trigger_pattern` and `response_pattern` are stored opaquely; the engine does not parse them
116
+ - `fire_event` uses the mapping's current strength (after `activate!`) as event intensity
117
+ - `prune_mappings!` evicts weakest by strength; `prune_weak_mappings!` removes strength-zero mappings after decay
118
+ - The event log is a ring buffer: `@events.shift while @events.size > MAX_EVENTS`
119
+ - `cross_modal_richness` counts unique source->target pairs (directional), not bidirectional
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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # lex-cognitive-synesthesia
2
+
3
+ A LegionIO cognitive architecture extension that models cross-modal cognitive associations inspired by synesthesia. Sensory mappings link one cognitive modality to another, firing automatically when the source modality is stimulated.
4
+
5
+ ## What It Does
6
+
7
+ Registers **sensory mappings** between eight cognitive modalities:
8
+
9
+ `visual`, `auditory`, `tactile`, `emotional`, `semantic`, `temporal`, `spatial`, `abstract`
10
+
11
+ When a source modality is triggered, all active mappings for it fire — generating `SynestheticEvent` records that carry the target response. Mappings strengthen with repeated activation and fade through decay, modeling the learned and habituated nature of associative responses.
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ require 'lex-cognitive-synesthesia'
17
+
18
+ client = Legion::Extensions::CognitiveSynesthesia::Client.new
19
+
20
+ # Register a mapping: emotional input evokes spatial response
21
+ client.register_mapping(
22
+ source_modality: :emotional,
23
+ target_modality: :spatial,
24
+ trigger_pattern: 'high_arousal',
25
+ response_pattern: 'open_wide_space'
26
+ )
27
+ # => { success: true, mapping_id: "uuid...", source_modality: :emotional, target_modality: :spatial }
28
+
29
+ # Register another mapping for the same source
30
+ client.register_mapping(
31
+ source_modality: :emotional,
32
+ target_modality: :visual,
33
+ trigger_pattern: 'high_arousal',
34
+ response_pattern: 'bright_warm_colors'
35
+ )
36
+
37
+ # Trigger all active mappings for emotional input
38
+ result = client.trigger(source_modality: :emotional, input: { valence: 0.8, arousal: 0.9 })
39
+ # => { success: true, triggered_count: 2, events: [ { intensity: 0.6, target_output: { modality: :spatial, ... } }, ... ] }
40
+
41
+ # Check cross-modal richness (breadth of coverage)
42
+ client.cross_modal_richness
43
+ # => { success: true, richness: 0.036, richness_label: :amodal }
44
+
45
+ # Which modality pairs are most activated?
46
+ client.dominant_modality_pairs(limit: 3)
47
+ # => { success: true, pairs: [{ pair: "emotional->spatial", activation_count: 1 }, ...], count: 2 }
48
+
49
+ # What modalities are covered by active mappings?
50
+ client.modality_coverage
51
+ # => { success: true, covered_modalities: [:emotional, :spatial, :visual], coverage_count: 3, total_modalities: 8 }
52
+
53
+ # Decay all mappings (removes those that fall to 0)
54
+ client.decay_mappings
55
+ # => { success: true, mappings_removed: 0, mappings_remaining: 2 }
56
+
57
+ # Recent event history
58
+ client.event_history(limit: 10)
59
+ # => { success: true, events: [...], count: 2 }
60
+
61
+ # Full report
62
+ client.synesthesia_report
63
+ # => { success: true, mapping_count: 2, active_mapping_count: 2, event_count: 2, cross_modal_richness: 0.036, ... }
64
+ ```
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ bundle install
70
+ bundle exec rspec
71
+ bundle exec rubocop
72
+ ```
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_synesthesia/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-synesthesia'
7
+ spec.version = Legion::Extensions::CognitiveSynesthesia::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Synesthesia'
12
+ spec.description = 'Cross-modal synesthetic mapping for LegionIO agents — automatic ' \
13
+ 'cross-domain associations that enrich understanding through sensory translation'
14
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-synesthesia'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 3.4'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-synesthesia'
20
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-synesthesia'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-synesthesia'
22
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-synesthesia/issues'
23
+ spec.metadata['rubygems_mfa_required'] = 'true'
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.require_paths = ['lib']
29
+ spec.add_development_dependency 'legion-gaia'
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_synesthesia/helpers/constants'
4
+ require 'legion/extensions/cognitive_synesthesia/helpers/sensory_mapping'
5
+ require 'legion/extensions/cognitive_synesthesia/helpers/synesthetic_event'
6
+ require 'legion/extensions/cognitive_synesthesia/helpers/synesthesia_engine'
7
+ require 'legion/extensions/cognitive_synesthesia/runners/cognitive_synesthesia'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitiveSynesthesia
12
+ class Client
13
+ include Runners::CognitiveSynesthesia
14
+
15
+ def initialize(engine: nil, **)
16
+ @synesthesia_engine = engine || Helpers::SynesthesiaEngine.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :synesthesia_engine
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynesthesia
6
+ module Helpers
7
+ module Constants
8
+ MAX_MAPPINGS = 200
9
+ MAX_EVENTS = 500
10
+ DEFAULT_STRENGTH = 0.5
11
+ STRENGTH_BOOST = 0.1
12
+ STRENGTH_DECAY = 0.02
13
+ TRIGGER_THRESHOLD = 0.3
14
+
15
+ MODALITIES = %i[visual auditory tactile emotional semantic temporal spatial abstract].freeze
16
+
17
+ STRENGTH_LABELS = {
18
+ (0.8..) => :dominant,
19
+ (0.6...0.8) => :strong,
20
+ (0.4...0.6) => :moderate,
21
+ (0.2...0.4) => :faint,
22
+ (..0.2) => :trace
23
+ }.freeze
24
+
25
+ RICHNESS_LABELS = {
26
+ (0.8..) => :synesthetic,
27
+ (0.6...0.8) => :vivid,
28
+ (0.4...0.6) => :partial,
29
+ (0.2...0.4) => :sparse,
30
+ (..0.2) => :amodal
31
+ }.freeze
32
+
33
+ INTENSITY_LABELS = {
34
+ (0.8..) => :overwhelming,
35
+ (0.6...0.8) => :intense,
36
+ (0.4...0.6) => :moderate,
37
+ (0.2...0.4) => :subtle,
38
+ (..0.2) => :subliminal
39
+ }.freeze
40
+
41
+ def self.label_for(labels_hash, value)
42
+ labels_hash.find { |range, _| range.cover?(value) }&.last || :unknown
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSynesthesia
8
+ module Helpers
9
+ class SensoryMapping
10
+ include Constants
11
+
12
+ attr_reader :id, :source_modality, :target_modality,
13
+ :trigger_pattern, :response_pattern,
14
+ :strength, :activation_count, :created_at, :last_activated_at
15
+
16
+ def initialize(source_modality:, target_modality:, trigger_pattern:, response_pattern:,
17
+ strength: DEFAULT_STRENGTH)
18
+ @id = SecureRandom.uuid
19
+ @source_modality = source_modality
20
+ @target_modality = target_modality
21
+ @trigger_pattern = trigger_pattern
22
+ @response_pattern = response_pattern
23
+ @strength = strength.clamp(0.0, 1.0)
24
+ @activation_count = 0
25
+ @created_at = Time.now.utc
26
+ @last_activated_at = nil
27
+ end
28
+
29
+ def activate!
30
+ @activation_count += 1
31
+ @last_activated_at = Time.now.utc
32
+ @strength = (@strength + STRENGTH_BOOST).clamp(0.0, 1.0).round(10)
33
+ end
34
+
35
+ def decay!
36
+ @strength = (@strength - STRENGTH_DECAY).clamp(0.0, 1.0).round(10)
37
+ end
38
+
39
+ def active?
40
+ @strength >= TRIGGER_THRESHOLD
41
+ end
42
+
43
+ def strength_label
44
+ Constants.label_for(STRENGTH_LABELS, @strength)
45
+ end
46
+
47
+ def to_h
48
+ {
49
+ id: @id,
50
+ source_modality: @source_modality,
51
+ target_modality: @target_modality,
52
+ trigger_pattern: @trigger_pattern,
53
+ response_pattern: @response_pattern,
54
+ strength: @strength,
55
+ activation_count: @activation_count,
56
+ strength_label: strength_label,
57
+ active: active?,
58
+ created_at: @created_at,
59
+ last_activated_at: @last_activated_at
60
+ }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynesthesia
6
+ module Helpers
7
+ class SynesthesiaEngine
8
+ include Constants
9
+
10
+ attr_reader :mappings, :events
11
+
12
+ def initialize
13
+ @mappings = {}
14
+ @events = []
15
+ end
16
+
17
+ def register_mapping(source_modality:, target_modality:, trigger_pattern:,
18
+ response_pattern:, strength: DEFAULT_STRENGTH, **)
19
+ return invalid_modality_error(:source_modality) unless MODALITIES.include?(source_modality)
20
+ return invalid_modality_error(:target_modality) unless MODALITIES.include?(target_modality)
21
+
22
+ mapping = SensoryMapping.new(
23
+ source_modality: source_modality,
24
+ target_modality: target_modality,
25
+ trigger_pattern: trigger_pattern,
26
+ response_pattern: response_pattern,
27
+ strength: strength
28
+ )
29
+
30
+ @mappings[mapping.id] = mapping
31
+ prune_mappings! if @mappings.size > MAX_MAPPINGS
32
+
33
+ Legion::Logging.debug "[cognitive_synesthesia] mapping registered id=#{mapping.id[0..7]} " \
34
+ "#{source_modality}->#{target_modality} strength=#{strength.round(2)}"
35
+
36
+ { success: true, mapping_id: mapping.id, source_modality: source_modality,
37
+ target_modality: target_modality }
38
+ end
39
+
40
+ def trigger(source_modality:, input:, **)
41
+ active = active_mappings_for(source_modality)
42
+ return { success: true, events: [], triggered_count: 0 } if active.empty?
43
+
44
+ triggered_events = active.map do |mapping|
45
+ fire_event(mapping, input)
46
+ end
47
+
48
+ { success: true, events: triggered_events.map(&:to_h), triggered_count: triggered_events.size }
49
+ end
50
+
51
+ def decay_mappings!(**)
52
+ @mappings.each_value(&:decay!)
53
+ removed = prune_weak_mappings!
54
+
55
+ Legion::Logging.debug "[cognitive_synesthesia] decay_mappings! removed=#{removed} remaining=#{@mappings.size}"
56
+ { success: true, mappings_removed: removed, mappings_remaining: @mappings.size }
57
+ end
58
+
59
+ def cross_modal_richness(**)
60
+ return 0.0 if @mappings.empty?
61
+
62
+ pairs = active_mapping_pairs
63
+ total_possible = (MODALITIES.size * (MODALITIES.size - 1)).to_f
64
+ return 0.0 if total_possible.zero?
65
+
66
+ (pairs.size.to_f / total_possible).clamp(0.0, 1.0).round(10)
67
+ end
68
+
69
+ def dominant_modality_pairs(limit: 5, **)
70
+ pair_counts = Hash.new(0)
71
+ @mappings.each_value do |m|
72
+ next unless m.active?
73
+
74
+ pair_counts["#{m.source_modality}->#{m.target_modality}"] += m.activation_count
75
+ end
76
+
77
+ sorted = pair_counts.sort_by { |_, count| -count }.first(limit)
78
+ sorted.map { |pair, count| { pair: pair, activation_count: count } }
79
+ end
80
+
81
+ def event_history(limit: 20, **)
82
+ recent = @events.last(limit)
83
+ { success: true, events: recent.map(&:to_h), count: recent.size }
84
+ end
85
+
86
+ def modality_coverage(**)
87
+ covered = Set.new
88
+ @mappings.each_value do |m|
89
+ next unless m.active?
90
+
91
+ covered << m.source_modality
92
+ covered << m.target_modality
93
+ end
94
+ {
95
+ covered_modalities: covered.to_a.sort,
96
+ coverage_count: covered.size,
97
+ total_modalities: MODALITIES.size
98
+ }
99
+ end
100
+
101
+ def synesthesia_report(**)
102
+ richness = cross_modal_richness
103
+ {
104
+ mapping_count: @mappings.size,
105
+ active_mapping_count: @mappings.values.count(&:active?),
106
+ event_count: @events.size,
107
+ cross_modal_richness: richness,
108
+ richness_label: Constants.label_for(RICHNESS_LABELS, richness),
109
+ dominant_modality_pairs: dominant_modality_pairs,
110
+ modality_coverage: modality_coverage
111
+ }
112
+ end
113
+
114
+ private
115
+
116
+ def invalid_modality_error(field)
117
+ { success: false, error: :invalid_modality, field: field, valid_modalities: MODALITIES }
118
+ end
119
+
120
+ def active_mappings_for(source_modality)
121
+ @mappings.values.select { |m| m.source_modality == source_modality && m.active? }
122
+ end
123
+
124
+ def fire_event(mapping, input)
125
+ mapping.activate!
126
+ intensity = mapping.strength.clamp(0.0, 1.0)
127
+ event = SynestheticEvent.new(
128
+ mapping_id: mapping.id,
129
+ source_input: input,
130
+ target_output: build_target_output(mapping, input),
131
+ intensity: intensity
132
+ )
133
+ @events << event
134
+ @events.shift while @events.size > MAX_EVENTS
135
+
136
+ Legion::Logging.debug "[cognitive_synesthesia] event fired id=#{event.id[0..7]} " \
137
+ "mapping=#{mapping.id[0..7]} intensity=#{intensity.round(2)}"
138
+ event
139
+ end
140
+
141
+ def build_target_output(mapping, input)
142
+ {
143
+ modality: mapping.target_modality,
144
+ response_pattern: mapping.response_pattern,
145
+ source_input: input,
146
+ source_modality: mapping.source_modality
147
+ }
148
+ end
149
+
150
+ def active_mapping_pairs
151
+ @mappings.values.select(&:active?).map do |m|
152
+ [m.source_modality, m.target_modality]
153
+ end.uniq
154
+ end
155
+
156
+ def prune_mappings!
157
+ overflow = @mappings.size - MAX_MAPPINGS
158
+ return if overflow <= 0
159
+
160
+ ids_to_prune = @mappings.min_by(overflow) { |_, m| m.strength }.map(&:first)
161
+ ids_to_prune.each { |id| @mappings.delete(id) }
162
+ end
163
+
164
+ def prune_weak_mappings!
165
+ before = @mappings.size
166
+ @mappings.reject! { |_, m| m.strength <= 0.0 }
167
+ before - @mappings.size
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSynesthesia
8
+ module Helpers
9
+ class SynestheticEvent
10
+ include Constants
11
+
12
+ attr_reader :id, :mapping_id, :source_input, :target_output,
13
+ :intensity, :involuntary, :triggered_at
14
+
15
+ def initialize(mapping_id:, source_input:, target_output:, intensity:, involuntary: true)
16
+ @id = SecureRandom.uuid
17
+ @mapping_id = mapping_id
18
+ @source_input = source_input
19
+ @target_output = target_output
20
+ @intensity = intensity.clamp(0.0, 1.0)
21
+ @involuntary = involuntary
22
+ @triggered_at = Time.now.utc
23
+ end
24
+
25
+ def intensity_label
26
+ Constants.label_for(INTENSITY_LABELS, @intensity)
27
+ end
28
+
29
+ def to_h
30
+ {
31
+ id: @id,
32
+ mapping_id: @mapping_id,
33
+ source_input: @source_input,
34
+ target_output: @target_output,
35
+ intensity: @intensity,
36
+ intensity_label: intensity_label,
37
+ involuntary: @involuntary,
38
+ triggered_at: @triggered_at
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynesthesia
6
+ module Runners
7
+ module CognitiveSynesthesia
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def register_mapping(source_modality:, target_modality:, trigger_pattern:,
12
+ response_pattern:, strength: Helpers::Constants::DEFAULT_STRENGTH,
13
+ engine: nil, **)
14
+ target = engine || synesthesia_engine
15
+ Legion::Logging.debug "[cognitive_synesthesia] runner register_mapping #{source_modality}->#{target_modality}"
16
+ target.register_mapping(
17
+ source_modality: source_modality,
18
+ target_modality: target_modality,
19
+ trigger_pattern: trigger_pattern,
20
+ response_pattern: response_pattern,
21
+ strength: strength
22
+ )
23
+ end
24
+
25
+ def trigger(source_modality:, input:, engine: nil, **)
26
+ target = engine || synesthesia_engine
27
+ Legion::Logging.debug "[cognitive_synesthesia] runner trigger modality=#{source_modality}"
28
+ target.trigger(source_modality: source_modality, input: input)
29
+ end
30
+
31
+ def decay_mappings(engine: nil, **)
32
+ target = engine || synesthesia_engine
33
+ Legion::Logging.debug '[cognitive_synesthesia] runner decay_mappings'
34
+ target.decay_mappings!
35
+ end
36
+
37
+ def cross_modal_richness(engine: nil, **)
38
+ target = engine || synesthesia_engine
39
+ richness = target.cross_modal_richness
40
+ label = Helpers::Constants.label_for(Helpers::Constants::RICHNESS_LABELS, richness)
41
+ Legion::Logging.debug "[cognitive_synesthesia] runner cross_modal_richness=#{richness.round(4)} label=#{label}"
42
+ { success: true, richness: richness, richness_label: label }
43
+ end
44
+
45
+ def dominant_modality_pairs(limit: 5, engine: nil, **)
46
+ target = engine || synesthesia_engine
47
+ pairs = target.dominant_modality_pairs(limit: limit)
48
+ Legion::Logging.debug "[cognitive_synesthesia] runner dominant_modality_pairs limit=#{limit}"
49
+ { success: true, pairs: pairs, count: pairs.size }
50
+ end
51
+
52
+ def event_history(limit: 20, engine: nil, **)
53
+ target = engine || synesthesia_engine
54
+ Legion::Logging.debug "[cognitive_synesthesia] runner event_history limit=#{limit}"
55
+ target.event_history(limit: limit)
56
+ end
57
+
58
+ def modality_coverage(engine: nil, **)
59
+ target = engine || synesthesia_engine
60
+ Legion::Logging.debug '[cognitive_synesthesia] runner modality_coverage'
61
+ { success: true }.merge(target.modality_coverage)
62
+ end
63
+
64
+ def synesthesia_report(engine: nil, **)
65
+ target = engine || synesthesia_engine
66
+ Legion::Logging.debug '[cognitive_synesthesia] runner synesthesia_report'
67
+ { success: true }.merge(target.synesthesia_report)
68
+ end
69
+
70
+ private
71
+
72
+ def synesthesia_engine
73
+ @synesthesia_engine ||= Helpers::SynesthesiaEngine.new
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynesthesia
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_synesthesia/version'
4
+ require 'legion/extensions/cognitive_synesthesia/helpers/constants'
5
+ require 'legion/extensions/cognitive_synesthesia/helpers/sensory_mapping'
6
+ require 'legion/extensions/cognitive_synesthesia/helpers/synesthetic_event'
7
+ require 'legion/extensions/cognitive_synesthesia/helpers/synesthesia_engine'
8
+ require 'legion/extensions/cognitive_synesthesia/runners/cognitive_synesthesia'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module CognitiveSynesthesia
13
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-synesthesia
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: Cross-modal synesthetic mapping for LegionIO agents — automatic cross-domain
27
+ associations that enrich understanding through sensory translation
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/ci.yml"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CLAUDE.md
39
+ - Gemfile
40
+ - README.md
41
+ - lex-cognitive-synesthesia.gemspec
42
+ - lib/legion/extensions/cognitive_synesthesia.rb
43
+ - lib/legion/extensions/cognitive_synesthesia/client.rb
44
+ - lib/legion/extensions/cognitive_synesthesia/helpers/constants.rb
45
+ - lib/legion/extensions/cognitive_synesthesia/helpers/sensory_mapping.rb
46
+ - lib/legion/extensions/cognitive_synesthesia/helpers/synesthesia_engine.rb
47
+ - lib/legion/extensions/cognitive_synesthesia/helpers/synesthetic_event.rb
48
+ - lib/legion/extensions/cognitive_synesthesia/runners/cognitive_synesthesia.rb
49
+ - lib/legion/extensions/cognitive_synesthesia/version.rb
50
+ homepage: https://github.com/LegionIO/lex-cognitive-synesthesia
51
+ licenses:
52
+ - MIT
53
+ metadata:
54
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-synesthesia
55
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-synesthesia
56
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-synesthesia
57
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-synesthesia
58
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-synesthesia/issues
59
+ rubygems_mfa_required: 'true'
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '3.4'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.6.9
75
+ specification_version: 4
76
+ summary: LEX Cognitive Synesthesia
77
+ test_files: []