lex-cognitive-liminal 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: '0871023ea17e12821fa4bc8c1734a6206e13ab67c3c6edeceedb749a36fd1e74'
4
+ data.tar.gz: 8e2b1dfce63884782db8293b0605fadd40e3edecfaef92cf92d4afd4e7cf923a
5
+ SHA512:
6
+ metadata.gz: 1bf9764fe3e730c9faf8f1e316f3d47561ecfa255be63dda7f891cd6dd940ec814fc1029f700798a513032fd7485b449e4ca2ebee6d47da1e4afbf391d097681
7
+ data.tar.gz: 0f9950a0098906cf1623cfad679f92ba211c3de35e13f20a48efd7d10161eaffb7a639d5abceefd91da3d25ed07cbe5b51e9fb43a2be7b4ecb3d9831a7dc5bb6
@@ -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,10 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ pkg
7
+ spec/reports
8
+ tmp
9
+ Gemfile.lock
10
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Naming/PredicateMethod:
9
+ Enabled: false
10
+
11
+ Naming/PredicatePrefix:
12
+ Enabled: false
13
+
14
+ Metrics/ClassLength:
15
+ Max: 150
16
+
17
+ Metrics/MethodLength:
18
+ Max: 25
19
+
20
+ Metrics/AbcSize:
21
+ Max: 25
22
+
23
+ Metrics/ParameterLists:
24
+ Max: 8
25
+ MaxOptionalParameters: 8
26
+
27
+ Layout/HashAlignment:
28
+ EnforcedColonStyle: table
29
+ EnforcedHashRocketStyle: table
30
+
31
+ Metrics/BlockLength:
32
+ Exclude:
33
+ - spec/**/*
34
+
35
+ Style/OneClassPerFile:
36
+ Exclude:
37
+ - spec/spec_helper.rb
data/CLAUDE.md ADDED
@@ -0,0 +1,116 @@
1
+ # lex-cognitive-liminal
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+
6
+ ## Purpose
7
+
8
+ Threshold crossing model based on anthropological liminality theory (van Gennep / Turner). Tracks cognitive transitions through three phases: `separation` (leaving the old state), `margin` (the liminal in-between), and `incorporation` (arriving at the new state). Each crossing tracks ambiguity (peaks at the liminal threshold), creative potential (highest in fertile liminal space), and progress (0.0 → 1.0).
9
+
10
+ ## Gem Info
11
+
12
+ - **Gem name**: `lex-cognitive-liminal`
13
+ - **Module**: `Legion::Extensions::CognitiveLiminal`
14
+ - **Version**: `0.1.0`
15
+ - **Ruby**: `>= 3.4`
16
+ - **License**: MIT
17
+
18
+ ## File Structure
19
+
20
+ ```
21
+ lib/legion/extensions/cognitive_liminal/
22
+ version.rb
23
+ client.rb
24
+ helpers/
25
+ constants.rb
26
+ threshold_crossing.rb
27
+ liminal_engine.rb
28
+ runners/
29
+ cognitive_liminal.rb
30
+ ```
31
+
32
+ ## Key Constants
33
+
34
+ | Constant | Value | Purpose |
35
+ |---|---|---|
36
+ | `MAX_TRANSITIONS` | `200` | Per-engine crossing capacity |
37
+ | `DEFAULT_AMBIGUITY` | `0.5` | Starting ambiguity for new crossings |
38
+ | `AMBIGUITY_GROWTH` | `0.08` | Ambiguity increase per `dissolve!` call |
39
+ | `AMBIGUITY_RESOLUTION` | `0.12` | Ambiguity decrease per `crystallize!` call |
40
+ | `CREATIVE_POTENTIAL_PEAK` | `0.7` | Creative potential at peak liminality |
41
+ | `DISSOLUTION_RATE` | `0.05` | Default progress regression rate |
42
+ | `CRYSTALLIZATION_RATE` | `0.07` | Default progress advancement rate |
43
+ | `SEPARATION_THRESHOLD` | `0.3` | Progress cutoff for separation phase |
44
+ | `LIMINAL_PEAK` | `0.7` | Progress value for peak liminality |
45
+ | `INCORPORATION_THRESHOLD` | `0.9` | Progress cutoff for incorporation phase |
46
+ | `TRANSITION_PHASES` | `%i[separation margin incorporation complete]` | Valid phases |
47
+ | `ORIGIN_STATES` / `DESTINATION_STATES` | symbol arrays | Valid state labels |
48
+ | `LIMINAL_QUALITIES` | symbol array | Tags for the in-between quality |
49
+
50
+ ## Helpers
51
+
52
+ ### `Helpers::ThresholdCrossing`
53
+ Single crossing instance. Has `id`, `origin_state`, `destination_state`, `domain`, `progress`, `ambiguity`, `creative_potential`, `phase`, and `completed_at`.
54
+
55
+ - `advance!(rate)` — increases progress via `CRYSTALLIZATION_RATE`, advances phase
56
+ - `dissolve!(rate)` — regresses progress, increases ambiguity
57
+ - `crystallize!(rate)` — advances progress, reduces ambiguity
58
+ - `complete!` — marks crossing done, sets `completed_at`
59
+ - `liminal?` — phase is `:margin`
60
+ - `separated?` — phase is `:separation`
61
+ - `incorporated?` — phase is `:incorporation` or `:complete`
62
+ - `peak_liminality?` — progress near `LIMINAL_PEAK`
63
+ - `fertile?` — creative potential above a threshold
64
+ - Label methods for ambiguity, creative potential, and phase
65
+
66
+ ### `Helpers::LiminalEngine`
67
+ Manages all crossings. Enforces `MAX_TRANSITIONS`.
68
+
69
+ - `begin_crossing(origin:, destination:, domain:)` → crossing
70
+ - `advance_crossing(crossing_id:)` → crossing state
71
+ - `dissolve_crossing(crossing_id:)` → crossing state
72
+ - `crystallize_crossing(crossing_id:)` → crossing state
73
+ - `active_crossings` → all incomplete crossings
74
+ - `completed_crossings` → all complete crossings
75
+ - `liminal_crossings` → crossings currently in margin phase
76
+ - `fertile_crossings` → crossings with high creative potential
77
+ - `peak_crossings` → crossings at peak liminality
78
+ - `crossings_by_domain(domain:)` → filtered list
79
+ - `average_ambiguity` → float
80
+ - `average_creative_potential` → float
81
+ - `liminal_density` → ratio of liminal to total active crossings
82
+ - `most_liminal` → crossing with highest ambiguity
83
+ - `liminal_report` → aggregate stats hash
84
+
85
+ ## Runners
86
+
87
+ Module: `Runners::CognitiveLiminal`
88
+
89
+ | Runner Method | Description |
90
+ |---|---|
91
+ | `begin_crossing(origin:, destination:, domain:)` | Start a new transition |
92
+ | `advance_crossing(crossing_id:)` | Push crossing forward |
93
+ | `dissolve_crossing(crossing_id:)` | Regress and increase ambiguity |
94
+ | `crystallize_crossing(crossing_id:)` | Advance and reduce ambiguity |
95
+ | `active_crossings` | All in-progress crossings |
96
+ | `liminal_crossings` | Crossings in margin phase |
97
+ | `fertile_crossings` | Crossings with high creative potential |
98
+ | `liminal_status` | Full aggregate report |
99
+
100
+ All runners return `{success: true/false, ...}` hashes.
101
+
102
+ ## Integration Points
103
+
104
+ - No direct dependencies on other agentic LEX gems
105
+ - Fits `lex-tick` `action_selection` phase: high ambiguity crossings can prompt consultation behavior
106
+ - Creative potential peaks can feed into `lex-emotion` as a positive arousal signal
107
+ - Peak liminality states represent prime conditions for insight — can trigger `lex-dream` association walks
108
+ - `lex-identity` entropy checks may coincide with high-ambiguity transitions
109
+
110
+ ## Development Notes
111
+
112
+ - `Client` instantiates `@liminal_engine = Helpers::LiminalEngine.new`
113
+ - Phase transitions are implicit: `advance!` checks progress thresholds against `SEPARATION_THRESHOLD`, `LIMINAL_PEAK`, and `INCORPORATION_THRESHOLD`
114
+ - A crossing can complete only by reaching `INCORPORATION_THRESHOLD` via `advance!` or `crystallize!`
115
+ - `fertile?` is true when creative potential >= `CREATIVE_POTENTIAL_PEAK * 0.9` (near peak)
116
+ - Ambiguity and creative potential are independent dimensions: high ambiguity + high creative potential = the fertile liminal state
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'
9
+ gem 'rubocop-rspec'
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # lex-cognitive-liminal
2
+
3
+ Threshold crossing model for LegionIO cognitive agents. Based on anthropological liminality theory, tracks transitions through separation, margin, and incorporation phases while measuring ambiguity and creative potential in the in-between space.
4
+
5
+ ## What It Does
6
+
7
+ - Begin crossings from an origin state to a destination state within a domain
8
+ - Three-phase progression: separation → margin (liminal) → incorporation
9
+ - Track ambiguity (peaks mid-transition) and creative potential (highest in fertile liminal space)
10
+ - `advance` / `crystallize` push crossings forward; `dissolve` regresses and increases ambiguity
11
+ - Identify fertile crossings (high creative potential), peak-liminality crossings, and domain clusters
12
+ - Supports up to 200 simultaneous transitions
13
+
14
+ ## Usage
15
+
16
+ ```ruby
17
+ # Start a crossing
18
+ result = runner.begin_crossing(
19
+ origin: :certainty, destination: :new_framework, domain: :architecture
20
+ )
21
+ crossing_id = result[:crossing][:id]
22
+
23
+ # Advance through phases
24
+ runner.advance_crossing(crossing_id: crossing_id)
25
+ # => { success: true, crossing: { phase: :separation, progress: 0.07, ambiguity: 0.5, ... } }
26
+
27
+ # In the margin — dissolve for more ambiguity (creative space)
28
+ runner.dissolve_crossing(crossing_id: crossing_id)
29
+
30
+ # Crystallize to resolve and advance
31
+ runner.crystallize_crossing(crossing_id: crossing_id)
32
+
33
+ # Find all crossings in the fertile liminal state
34
+ runner.fertile_crossings
35
+ # => { success: true, crossings: [...], count: N }
36
+
37
+ # Overall status
38
+ runner.liminal_status
39
+ # => { success: true, total_active: N, liminal_count: N, avg_ambiguity: 0.XX, avg_creative_potential: 0.XX, ... }
40
+ ```
41
+
42
+ ## Development
43
+
44
+ ```bash
45
+ bundle install
46
+ bundle exec rspec
47
+ bundle exec rubocop
48
+ ```
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_liminal/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-liminal'
7
+ spec.version = Legion::Extensions::CognitiveLiminal::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+ spec.summary = 'Liminal threshold states between cognitive modes for LegionIO agents'
11
+ spec.description = 'Models liminality — the creative in-between states during cognitive ' \
12
+ 'mode transitions with ambiguity, dissolution, and crystallization'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-liminal'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata = {
18
+ 'homepage_uri' => spec.homepage,
19
+ 'source_code_uri' => spec.homepage,
20
+ 'documentation_uri' => "#{spec.homepage}/blob/origin/README.md",
21
+ 'changelog_uri' => "#{spec.homepage}/blob/origin/CHANGELOG.md",
22
+ 'bug_tracker_uri' => "#{spec.homepage}/issues",
23
+ 'rubygems_mfa_required' => 'true'
24
+ }
25
+
26
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'legion-gaia'
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveLiminal
6
+ class Client
7
+ include Runners::CognitiveLiminal
8
+
9
+ def initialize
10
+ @default_engine = Helpers::LiminalEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveLiminal
6
+ module Helpers
7
+ module Constants
8
+ MAX_TRANSITIONS = 200
9
+ MAX_THRESHOLDS = 100
10
+
11
+ # Liminal dynamics
12
+ DEFAULT_AMBIGUITY = 0.5
13
+ AMBIGUITY_GROWTH = 0.08
14
+ AMBIGUITY_RESOLUTION = 0.12
15
+ CREATIVE_POTENTIAL_PEAK = 0.7
16
+ DISSOLUTION_RATE = 0.05
17
+ CRYSTALLIZATION_RATE = 0.07
18
+
19
+ # Phase thresholds
20
+ SEPARATION_THRESHOLD = 0.3
21
+ LIMINAL_PEAK = 0.7
22
+ INCORPORATION_THRESHOLD = 0.9
23
+
24
+ TRANSITION_PHASES = %i[
25
+ separation margin incorporation
26
+ ].freeze
27
+
28
+ ORIGIN_STATES = %i[
29
+ analytical creative intuitive deliberate
30
+ receptive focused diffuse dormant
31
+ ].freeze
32
+
33
+ DESTINATION_STATES = %i[
34
+ analytical creative intuitive deliberate
35
+ receptive focused diffuse dormant
36
+ ].freeze
37
+
38
+ LIMINAL_QUALITIES = %i[
39
+ ambiguous fertile dissolving crystallizing
40
+ betwixt chaotic generative suspended
41
+ ].freeze
42
+
43
+ AMBIGUITY_LABELS = {
44
+ (0.8..) => :maximally_liminal,
45
+ (0.6...0.8) => :deeply_liminal,
46
+ (0.4...0.6) => :liminal,
47
+ (0.2...0.4) => :transitioning,
48
+ (..0.2) => :settled
49
+ }.freeze
50
+
51
+ POTENTIAL_LABELS = {
52
+ (0.8..) => :explosive,
53
+ (0.6...0.8) => :high,
54
+ (0.4...0.6) => :moderate,
55
+ (0.2...0.4) => :low,
56
+ (..0.2) => :depleted
57
+ }.freeze
58
+
59
+ PROGRESS_LABELS = {
60
+ (0.8..) => :nearly_incorporated,
61
+ (0.6...0.8) => :deep_margin,
62
+ (0.4...0.6) => :peak_liminality,
63
+ (0.2...0.4) => :early_separation,
64
+ (..0.2) => :pre_liminal
65
+ }.freeze
66
+
67
+ def self.label_for(labels, value)
68
+ labels.each { |range, label| return label if range.cover?(value) }
69
+ :unknown
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveLiminal
6
+ module Helpers
7
+ class LiminalEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @crossings = {}
12
+ end
13
+
14
+ def begin_crossing(origin:, destination:, domain: :general, ambiguity: DEFAULT_AMBIGUITY)
15
+ prune_completed
16
+ crossing = ThresholdCrossing.new(origin: origin, destination: destination,
17
+ domain: domain, ambiguity: ambiguity)
18
+ @crossings[crossing.id] = crossing
19
+ crossing
20
+ end
21
+
22
+ def advance_crossing(crossing_id:)
23
+ crossing = @crossings[crossing_id]
24
+ return nil unless crossing
25
+
26
+ crossing.advance!
27
+ end
28
+
29
+ def dissolve_crossing(crossing_id:)
30
+ crossing = @crossings[crossing_id]
31
+ return nil unless crossing
32
+
33
+ crossing.dissolve!
34
+ end
35
+
36
+ def crystallize_crossing(crossing_id:)
37
+ crossing = @crossings[crossing_id]
38
+ return nil unless crossing
39
+
40
+ crossing.crystallize!
41
+ end
42
+
43
+ def active_crossings = @crossings.values.select { |c| c.status == :active }
44
+ def completed_crossings = @crossings.values.select(&:incorporated?)
45
+ def liminal_crossings = @crossings.values.select(&:liminal?)
46
+ def fertile_crossings = @crossings.values.select(&:fertile?)
47
+ def peak_crossings = @crossings.values.select(&:peak_liminality?)
48
+
49
+ def crossings_by_domain(domain:)
50
+ @crossings.values.select { |c| c.domain == domain.to_sym }
51
+ end
52
+
53
+ def average_ambiguity
54
+ active = active_crossings
55
+ return 0.0 if active.empty?
56
+
57
+ (active.sum(&:ambiguity) / active.size).round(10)
58
+ end
59
+
60
+ def average_creative_potential
61
+ active = active_crossings
62
+ return 0.0 if active.empty?
63
+
64
+ (active.sum(&:creative_potential) / active.size).round(10)
65
+ end
66
+
67
+ def liminal_density
68
+ return 0.0 if @crossings.empty?
69
+
70
+ (liminal_crossings.size.to_f / [@crossings.size, 1].max).clamp(0.0, 1.0).round(10)
71
+ end
72
+
73
+ def most_liminal(limit: 5) = @crossings.values.sort_by { |c| -c.ambiguity }.first(limit)
74
+
75
+ def liminal_report
76
+ {
77
+ total_crossings: @crossings.size,
78
+ active_count: active_crossings.size,
79
+ liminal_count: liminal_crossings.size,
80
+ completed_count: completed_crossings.size,
81
+ fertile_count: fertile_crossings.size,
82
+ peak_count: peak_crossings.size,
83
+ average_ambiguity: average_ambiguity,
84
+ average_potential: average_creative_potential,
85
+ liminal_density: liminal_density,
86
+ most_liminal: most_liminal(limit: 3).map(&:to_h)
87
+ }
88
+ end
89
+
90
+ def to_h
91
+ {
92
+ total_crossings: @crossings.size,
93
+ active: active_crossings.size,
94
+ liminal: liminal_crossings.size,
95
+ avg_ambiguity: average_ambiguity,
96
+ avg_potential: average_creative_potential
97
+ }
98
+ end
99
+
100
+ private
101
+
102
+ def prune_completed
103
+ return if @crossings.size < MAX_TRANSITIONS
104
+
105
+ @crossings.reject! { |_, c| c.incorporated? }
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveLiminal
8
+ module Helpers
9
+ class ThresholdCrossing
10
+ include Constants
11
+
12
+ attr_reader :id, :origin, :destination, :domain, :phase,
13
+ :ambiguity, :creative_potential, :progress,
14
+ :quality, :created_at, :ticks_in_liminal
15
+ attr_accessor :status
16
+
17
+ def initialize(origin:, destination:, domain: :general, ambiguity: DEFAULT_AMBIGUITY)
18
+ @id = SecureRandom.uuid
19
+ @origin = valid_state(origin, ORIGIN_STATES)
20
+ @destination = valid_state(destination, DESTINATION_STATES)
21
+ @domain = domain.to_sym
22
+ @ambiguity = ambiguity.to_f.clamp(0.0, 1.0).round(10)
23
+ @creative_potential = compute_creative_potential
24
+ @progress = 0.0
25
+ @phase = :separation
26
+ @quality = :ambiguous
27
+ @status = :active
28
+ @ticks_in_liminal = 0
29
+ @created_at = Time.now
30
+ end
31
+
32
+ def advance!
33
+ return self if @status == :completed
34
+
35
+ @ticks_in_liminal += 1
36
+ @progress = (@progress + CRYSTALLIZATION_RATE).clamp(0.0, 1.0).round(10)
37
+ update_phase!
38
+ update_ambiguity!
39
+ @creative_potential = compute_creative_potential
40
+ self
41
+ end
42
+
43
+ def dissolve!
44
+ @ambiguity = (@ambiguity + DISSOLUTION_RATE).clamp(0.0, 1.0).round(10)
45
+ @creative_potential = compute_creative_potential
46
+ @quality = :dissolving
47
+ self
48
+ end
49
+
50
+ def crystallize!
51
+ @ambiguity = (@ambiguity - AMBIGUITY_RESOLUTION).clamp(0.0, 1.0).round(10)
52
+ @progress = (@progress + CRYSTALLIZATION_RATE).clamp(0.0, 1.0).round(10)
53
+ @quality = :crystallizing
54
+ update_phase!
55
+ self
56
+ end
57
+
58
+ def complete!
59
+ @status = :completed
60
+ @phase = :incorporation
61
+ @progress = 1.0
62
+ @ambiguity = 0.0
63
+ @quality = :crystallizing
64
+ self
65
+ end
66
+
67
+ def liminal? = @phase == :margin && @status == :active
68
+ def separated? = @phase == :separation
69
+ def incorporated? = @status == :completed
70
+ def peak_liminality? = @ambiguity >= LIMINAL_PEAK
71
+ def fertile? = @creative_potential >= CREATIVE_POTENTIAL_PEAK
72
+
73
+ def ambiguity_label = Constants.label_for(AMBIGUITY_LABELS, @ambiguity)
74
+ def potential_label = Constants.label_for(POTENTIAL_LABELS, @creative_potential)
75
+ def progress_label = Constants.label_for(PROGRESS_LABELS, @progress)
76
+
77
+ def to_h
78
+ {
79
+ id: @id,
80
+ origin: @origin,
81
+ destination: @destination,
82
+ domain: @domain,
83
+ phase: @phase,
84
+ ambiguity: @ambiguity,
85
+ creative_potential: @creative_potential,
86
+ progress: @progress,
87
+ quality: @quality,
88
+ status: @status,
89
+ ticks_in_liminal: @ticks_in_liminal,
90
+ liminal: liminal?,
91
+ peak_liminality: peak_liminality?,
92
+ fertile: fertile?,
93
+ ambiguity_label: ambiguity_label,
94
+ potential_label: potential_label,
95
+ progress_label: progress_label,
96
+ created_at: @created_at.iso8601
97
+ }
98
+ end
99
+
100
+ private
101
+
102
+ def compute_creative_potential
103
+ peak_dist = (@ambiguity - CREATIVE_POTENTIAL_PEAK).abs
104
+ (1.0 - peak_dist).clamp(0.0, 1.0).round(10)
105
+ end
106
+
107
+ def update_phase!
108
+ if @progress >= INCORPORATION_THRESHOLD
109
+ @phase = :incorporation
110
+ complete!
111
+ elsif @progress >= SEPARATION_THRESHOLD
112
+ @phase = :margin
113
+ end
114
+ end
115
+
116
+ def update_ambiguity!
117
+ if @phase == :margin
118
+ @ambiguity = (@ambiguity + AMBIGUITY_GROWTH).clamp(0.0, 1.0).round(10)
119
+ elsif @phase == :incorporation
120
+ @ambiguity = (@ambiguity - AMBIGUITY_RESOLUTION).clamp(0.0, 1.0).round(10)
121
+ end
122
+ end
123
+
124
+ def valid_state(state, list)
125
+ sym = state.to_sym
126
+ list.include?(sym) ? sym : list.first
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveLiminal
6
+ module Runners
7
+ module CognitiveLiminal
8
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+
10
+ def begin_crossing(origin:, destination:, domain: :general, ambiguity: nil, engine: nil, **)
11
+ eng = engine || @default_engine
12
+ crossing = eng.begin_crossing(origin: origin, destination: destination, domain: domain,
13
+ ambiguity: ambiguity || Helpers::Constants::DEFAULT_AMBIGUITY)
14
+ { success: true, crossing: crossing.to_h }
15
+ end
16
+
17
+ def advance_crossing(crossing_id:, engine: nil, **)
18
+ eng = engine || @default_engine
19
+ crossing = eng.advance_crossing(crossing_id: crossing_id)
20
+ return { success: false, error: 'crossing not found' } unless crossing
21
+
22
+ { success: true, crossing: crossing.to_h }
23
+ end
24
+
25
+ def dissolve_crossing(crossing_id:, engine: nil, **)
26
+ eng = engine || @default_engine
27
+ crossing = eng.dissolve_crossing(crossing_id: crossing_id)
28
+ return { success: false, error: 'crossing not found' } unless crossing
29
+
30
+ { success: true, crossing: crossing.to_h }
31
+ end
32
+
33
+ def crystallize_crossing(crossing_id:, engine: nil, **)
34
+ eng = engine || @default_engine
35
+ crossing = eng.crystallize_crossing(crossing_id: crossing_id)
36
+ return { success: false, error: 'crossing not found' } unless crossing
37
+
38
+ { success: true, crossing: crossing.to_h }
39
+ end
40
+
41
+ def active_crossings(engine: nil, **)
42
+ eng = engine || @default_engine
43
+ crossings = eng.active_crossings
44
+ { success: true, count: crossings.size, crossings: crossings.map(&:to_h) }
45
+ end
46
+
47
+ def liminal_crossings(engine: nil, **)
48
+ eng = engine || @default_engine
49
+ crossings = eng.liminal_crossings
50
+ { success: true, count: crossings.size, crossings: crossings.map(&:to_h) }
51
+ end
52
+
53
+ def fertile_crossings(engine: nil, **)
54
+ eng = engine || @default_engine
55
+ crossings = eng.fertile_crossings
56
+ { success: true, count: crossings.size, crossings: crossings.map(&:to_h) }
57
+ end
58
+
59
+ def liminal_status(engine: nil, **)
60
+ eng = engine || @default_engine
61
+ report = eng.liminal_report
62
+ { success: true, **report }
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveLiminal
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cognitive_liminal/version'
4
+ require_relative 'cognitive_liminal/helpers/constants'
5
+ require_relative 'cognitive_liminal/helpers/threshold_crossing'
6
+ require_relative 'cognitive_liminal/helpers/liminal_engine'
7
+ require_relative 'cognitive_liminal/runners/cognitive_liminal'
8
+ require_relative 'cognitive_liminal/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module CognitiveLiminal
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveLiminal::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'responds to runner methods' do
7
+ expect(client).to respond_to(:begin_crossing, :advance_crossing, :liminal_status)
8
+ end
9
+
10
+ it 'runs a full liminal lifecycle' do
11
+ result = client.begin_crossing(origin: :analytical, destination: :creative, domain: :cognitive)
12
+ crossing_id = result[:crossing][:id]
13
+
14
+ 5.times { client.advance_crossing(crossing_id: crossing_id) }
15
+ client.dissolve_crossing(crossing_id: crossing_id)
16
+ 10.times { client.crystallize_crossing(crossing_id: crossing_id) }
17
+
18
+ status = client.liminal_status
19
+ expect(status[:total_crossings]).to eq(1)
20
+ end
21
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveLiminal::Helpers::LiminalEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ describe '#begin_crossing' do
7
+ it 'creates a threshold crossing' do
8
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
9
+ expect(crossing.origin).to eq(:analytical)
10
+ end
11
+ end
12
+
13
+ describe '#advance_crossing' do
14
+ it 'advances known crossing' do
15
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
16
+ engine.advance_crossing(crossing_id: crossing.id)
17
+ expect(crossing.ticks_in_liminal).to eq(1)
18
+ end
19
+
20
+ it 'returns nil for unknown crossing' do
21
+ expect(engine.advance_crossing(crossing_id: 'bad')).to be_nil
22
+ end
23
+ end
24
+
25
+ describe '#dissolve_crossing' do
26
+ it 'dissolves known crossing' do
27
+ crossing = engine.begin_crossing(origin: :focused, destination: :diffuse)
28
+ original = crossing.ambiguity
29
+ engine.dissolve_crossing(crossing_id: crossing.id)
30
+ expect(crossing.ambiguity).to be > original
31
+ end
32
+ end
33
+
34
+ describe '#crystallize_crossing' do
35
+ it 'crystallizes known crossing' do
36
+ crossing = engine.begin_crossing(origin: :intuitive, destination: :deliberate)
37
+ original = crossing.ambiguity
38
+ engine.crystallize_crossing(crossing_id: crossing.id)
39
+ expect(crossing.ambiguity).to be < original
40
+ end
41
+ end
42
+
43
+ describe '#active_crossings' do
44
+ it 'returns active crossings' do
45
+ engine.begin_crossing(origin: :analytical, destination: :creative)
46
+ expect(engine.active_crossings.size).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe '#completed_crossings' do
51
+ it 'returns completed crossings' do
52
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
53
+ 20.times { crossing.advance! }
54
+ expect(engine.completed_crossings.size).to eq(1)
55
+ end
56
+ end
57
+
58
+ describe '#liminal_crossings' do
59
+ it 'returns crossings in margin phase' do
60
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
61
+ 5.times { crossing.advance! }
62
+ expect(engine.liminal_crossings.size).to eq(1)
63
+ end
64
+ end
65
+
66
+ describe '#fertile_crossings' do
67
+ it 'returns fertile crossings' do
68
+ engine.begin_crossing(origin: :analytical, destination: :creative)
69
+ expect(engine.fertile_crossings.size).to eq(1)
70
+ end
71
+ end
72
+
73
+ describe '#crossings_by_domain' do
74
+ it 'filters by domain' do
75
+ engine.begin_crossing(origin: :analytical, destination: :creative, domain: :cognitive)
76
+ engine.begin_crossing(origin: :focused, destination: :diffuse, domain: :emotional)
77
+ expect(engine.crossings_by_domain(domain: :cognitive).size).to eq(1)
78
+ end
79
+ end
80
+
81
+ describe '#average_ambiguity' do
82
+ it 'returns 0.0 with no active crossings' do
83
+ expect(engine.average_ambiguity).to eq(0.0)
84
+ end
85
+
86
+ it 'returns average for active crossings' do
87
+ engine.begin_crossing(origin: :analytical, destination: :creative, ambiguity: 0.4)
88
+ engine.begin_crossing(origin: :focused, destination: :diffuse, ambiguity: 0.6)
89
+ expect(engine.average_ambiguity).to eq(0.5)
90
+ end
91
+ end
92
+
93
+ describe '#average_creative_potential' do
94
+ it 'returns 0.0 with no active crossings' do
95
+ expect(engine.average_creative_potential).to eq(0.0)
96
+ end
97
+ end
98
+
99
+ describe '#liminal_density' do
100
+ it 'returns 0.0 with no crossings' do
101
+ expect(engine.liminal_density).to eq(0.0)
102
+ end
103
+ end
104
+
105
+ describe '#most_liminal' do
106
+ it 'returns sorted by ambiguity descending' do
107
+ engine.begin_crossing(origin: :analytical, destination: :creative, ambiguity: 0.3)
108
+ high = engine.begin_crossing(origin: :focused, destination: :diffuse, ambiguity: 0.8)
109
+ expect(engine.most_liminal(limit: 1).first.id).to eq(high.id)
110
+ end
111
+ end
112
+
113
+ describe '#liminal_report' do
114
+ it 'includes all report fields' do
115
+ report = engine.liminal_report
116
+ expect(report).to include(
117
+ :total_crossings, :active_count, :liminal_count, :completed_count,
118
+ :fertile_count, :peak_count, :average_ambiguity, :average_potential,
119
+ :liminal_density, :most_liminal
120
+ )
121
+ end
122
+ end
123
+
124
+ describe '#to_h' do
125
+ it 'includes summary fields' do
126
+ hash = engine.to_h
127
+ expect(hash).to include(:total_crossings, :active, :liminal, :avg_ambiguity, :avg_potential)
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveLiminal::Helpers::ThresholdCrossing do
4
+ subject(:crossing) { described_class.new(origin: :analytical, destination: :creative) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID id' do
8
+ expect(crossing.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores origin' do
12
+ expect(crossing.origin).to eq(:analytical)
13
+ end
14
+
15
+ it 'stores destination' do
16
+ expect(crossing.destination).to eq(:creative)
17
+ end
18
+
19
+ it 'defaults to separation phase' do
20
+ expect(crossing.phase).to eq(:separation)
21
+ end
22
+
23
+ it 'defaults ambiguity' do
24
+ expect(crossing.ambiguity).to eq(0.5)
25
+ end
26
+
27
+ it 'starts with active status' do
28
+ expect(crossing.status).to eq(:active)
29
+ end
30
+
31
+ it 'starts with 0 ticks' do
32
+ expect(crossing.ticks_in_liminal).to eq(0)
33
+ end
34
+
35
+ it 'validates origin state' do
36
+ bad = described_class.new(origin: :nonexistent, destination: :creative)
37
+ expect(bad.origin).to eq(:analytical)
38
+ end
39
+ end
40
+
41
+ describe '#advance!' do
42
+ it 'increments ticks' do
43
+ crossing.advance!
44
+ expect(crossing.ticks_in_liminal).to eq(1)
45
+ end
46
+
47
+ it 'increases progress' do
48
+ original = crossing.progress
49
+ crossing.advance!
50
+ expect(crossing.progress).to be > original
51
+ end
52
+
53
+ it 'transitions to margin phase' do
54
+ 5.times { crossing.advance! }
55
+ expect(crossing.phase).to eq(:margin)
56
+ end
57
+
58
+ it 'eventually completes' do
59
+ 20.times { crossing.advance! }
60
+ expect(crossing.incorporated?).to be true
61
+ end
62
+
63
+ it 'does not advance completed crossings' do
64
+ 20.times { crossing.advance! }
65
+ ticks = crossing.ticks_in_liminal
66
+ crossing.advance!
67
+ expect(crossing.ticks_in_liminal).to eq(ticks)
68
+ end
69
+ end
70
+
71
+ describe '#dissolve!' do
72
+ it 'increases ambiguity' do
73
+ original = crossing.ambiguity
74
+ crossing.dissolve!
75
+ expect(crossing.ambiguity).to be > original
76
+ end
77
+
78
+ it 'sets quality to dissolving' do
79
+ crossing.dissolve!
80
+ expect(crossing.quality).to eq(:dissolving)
81
+ end
82
+ end
83
+
84
+ describe '#crystallize!' do
85
+ it 'decreases ambiguity' do
86
+ original = crossing.ambiguity
87
+ crossing.crystallize!
88
+ expect(crossing.ambiguity).to be < original
89
+ end
90
+
91
+ it 'increases progress' do
92
+ original = crossing.progress
93
+ crossing.crystallize!
94
+ expect(crossing.progress).to be > original
95
+ end
96
+
97
+ it 'sets quality to crystallizing' do
98
+ crossing.crystallize!
99
+ expect(crossing.quality).to eq(:crystallizing)
100
+ end
101
+ end
102
+
103
+ describe '#complete!' do
104
+ it 'sets status to completed' do
105
+ crossing.complete!
106
+ expect(crossing.status).to eq(:completed)
107
+ end
108
+
109
+ it 'sets progress to 1.0' do
110
+ crossing.complete!
111
+ expect(crossing.progress).to eq(1.0)
112
+ end
113
+
114
+ it 'clears ambiguity' do
115
+ crossing.complete!
116
+ expect(crossing.ambiguity).to eq(0.0)
117
+ end
118
+ end
119
+
120
+ describe '#liminal?' do
121
+ it 'is false in separation' do
122
+ expect(crossing.liminal?).to be false
123
+ end
124
+
125
+ it 'is true in margin phase' do
126
+ 5.times { crossing.advance! }
127
+ expect(crossing.liminal?).to be true
128
+ end
129
+ end
130
+
131
+ describe '#peak_liminality?' do
132
+ it 'is true when ambiguity is high' do
133
+ 5.times { crossing.dissolve! }
134
+ expect(crossing.peak_liminality?).to be true
135
+ end
136
+ end
137
+
138
+ describe '#fertile?' do
139
+ it 'detects creative potential peak' do
140
+ expect(crossing.fertile?).to be true
141
+ end
142
+ end
143
+
144
+ describe '#to_h' do
145
+ it 'includes all fields' do
146
+ hash = crossing.to_h
147
+ expect(hash).to include(
148
+ :id, :origin, :destination, :domain, :phase, :ambiguity,
149
+ :creative_potential, :progress, :quality, :status,
150
+ :ticks_in_liminal, :liminal, :peak_liminality, :fertile,
151
+ :ambiguity_label, :potential_label, :progress_label, :created_at
152
+ )
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveLiminal::Runners::CognitiveLiminal do
4
+ let(:engine) { Legion::Extensions::CognitiveLiminal::Helpers::LiminalEngine.new }
5
+ let(:runner) do
6
+ obj = Object.new
7
+ obj.extend(described_class)
8
+ obj.instance_variable_set(:@default_engine, engine)
9
+ obj
10
+ end
11
+
12
+ describe '#begin_crossing' do
13
+ it 'returns success with crossing hash' do
14
+ result = runner.begin_crossing(origin: :analytical, destination: :creative, engine: engine)
15
+ expect(result[:success]).to be true
16
+ expect(result[:crossing][:origin]).to eq(:analytical)
17
+ end
18
+ end
19
+
20
+ describe '#advance_crossing' do
21
+ it 'returns success for known crossing' do
22
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
23
+ result = runner.advance_crossing(crossing_id: crossing.id, engine: engine)
24
+ expect(result[:success]).to be true
25
+ end
26
+
27
+ it 'returns failure for unknown crossing' do
28
+ result = runner.advance_crossing(crossing_id: 'bad', engine: engine)
29
+ expect(result[:success]).to be false
30
+ end
31
+ end
32
+
33
+ describe '#dissolve_crossing' do
34
+ it 'returns success for known crossing' do
35
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
36
+ result = runner.dissolve_crossing(crossing_id: crossing.id, engine: engine)
37
+ expect(result[:success]).to be true
38
+ end
39
+ end
40
+
41
+ describe '#crystallize_crossing' do
42
+ it 'returns success for known crossing' do
43
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
44
+ result = runner.crystallize_crossing(crossing_id: crossing.id, engine: engine)
45
+ expect(result[:success]).to be true
46
+ end
47
+ end
48
+
49
+ describe '#active_crossings' do
50
+ it 'returns active list' do
51
+ engine.begin_crossing(origin: :analytical, destination: :creative)
52
+ result = runner.active_crossings(engine: engine)
53
+ expect(result[:count]).to eq(1)
54
+ end
55
+ end
56
+
57
+ describe '#liminal_crossings' do
58
+ it 'returns liminal list' do
59
+ crossing = engine.begin_crossing(origin: :analytical, destination: :creative)
60
+ 5.times { crossing.advance! }
61
+ result = runner.liminal_crossings(engine: engine)
62
+ expect(result[:count]).to eq(1)
63
+ end
64
+ end
65
+
66
+ describe '#fertile_crossings' do
67
+ it 'returns fertile list' do
68
+ engine.begin_crossing(origin: :analytical, destination: :creative)
69
+ result = runner.fertile_crossings(engine: engine)
70
+ expect(result[:count]).to eq(1)
71
+ end
72
+ end
73
+
74
+ describe '#liminal_status' do
75
+ it 'returns comprehensive status' do
76
+ result = runner.liminal_status(engine: engine)
77
+ expect(result[:success]).to be true
78
+ expect(result).to include(:total_crossings, :liminal_density)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveLiminal do
4
+ it 'has a version number' do
5
+ expect(Legion::Extensions::CognitiveLiminal::VERSION).to eq('0.1.0')
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_liminal'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Helpers
8
+ module Lex; end
9
+ end
10
+ end
11
+
12
+ module Logging
13
+ def self.method_missing(_, *) = nil
14
+ def self.respond_to_missing?(_, _ = false) = true
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.expect_with :rspec do |expectations|
20
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
21
+ end
22
+ config.mock_with :rspec do |mocks|
23
+ mocks.verify_partial_doubles = true
24
+ end
25
+ config.shared_context_metadata_behavior = :apply_to_host_groups
26
+ config.order = :random
27
+ Kernel.srand config.seed
28
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-liminal
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: Models liminality — the creative in-between states during cognitive mode
27
+ transitions with ambiguity, dissolution, and crystallization
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-liminal.gemspec
42
+ - lib/legion/extensions/cognitive_liminal.rb
43
+ - lib/legion/extensions/cognitive_liminal/client.rb
44
+ - lib/legion/extensions/cognitive_liminal/helpers/constants.rb
45
+ - lib/legion/extensions/cognitive_liminal/helpers/liminal_engine.rb
46
+ - lib/legion/extensions/cognitive_liminal/helpers/threshold_crossing.rb
47
+ - lib/legion/extensions/cognitive_liminal/runners/cognitive_liminal.rb
48
+ - lib/legion/extensions/cognitive_liminal/version.rb
49
+ - spec/legion/extensions/cognitive_liminal/client_spec.rb
50
+ - spec/legion/extensions/cognitive_liminal/helpers/liminal_engine_spec.rb
51
+ - spec/legion/extensions/cognitive_liminal/helpers/threshold_crossing_spec.rb
52
+ - spec/legion/extensions/cognitive_liminal/runners_spec.rb
53
+ - spec/legion/extensions/cognitive_liminal_spec.rb
54
+ - spec/spec_helper.rb
55
+ homepage: https://github.com/LegionIO/lex-cognitive-liminal
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-liminal
60
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-liminal
61
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-liminal/blob/origin/README.md
62
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-liminal/blob/origin/CHANGELOG.md
63
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-liminal/issues
64
+ rubygems_mfa_required: 'true'
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '3.4'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.6.9
80
+ specification_version: 4
81
+ summary: Liminal threshold states between cognitive modes for LegionIO agents
82
+ test_files: []