lex-cognitive-alchemy 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: 51513cbacc1a1f0aecbdd72f1b383e406941930fe87cbe1ecc56c956f8fab8eb
4
+ data.tar.gz: c1e9114adbc5ccaeade7e72c6f0f5d565028bf2d0d642e3d9e12ff81fdd71ddb
5
+ SHA512:
6
+ metadata.gz: 062f264dfbfc751c3f8224b5b124ad599058705308e626c8d87ed0c672171c161d56d74dddf53a67cc9d9366c819863edaddbe417f5f1eaa14462871766048be
7
+ data.tar.gz: 3785118d7f7c03f2ea41a6da09c27c18bae76580cb99cc6590f5cc30eef1f1c7e09a985d9580b7876780681fd5fb91b17264ce9da2d6382157dea3736d234321
@@ -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,2 @@
1
+ .rspec_status
2
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
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,87 @@
1
+ # lex-cognitive-alchemy
2
+
3
+ **Level 3 Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
6
+
7
+ ## Purpose
8
+
9
+ Alchemical metaphor for cognitive transformation — substances move through four Jungian stages (nigredo, albedo, citrinitas, rubedo) via reactions and catalysts, modeling irreversible knowledge refinement and insight synthesis.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-cognitive-alchemy`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::CognitiveAlchemy`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/cognitive_alchemy/
23
+ cognitive_alchemy.rb # Main extension module
24
+ version.rb # VERSION = '0.1.0'
25
+ client.rb # Client wrapper
26
+ helpers/
27
+ constants.rb # Stages, element types, reaction types, multipliers, labels
28
+ substance.rb # Substance value object (stage, purity, potency, element_type)
29
+ alchemy_engine.rb # AlchemyEngine — manages substances, reactions, stage progression
30
+ runners/
31
+ cognitive_alchemy.rb # Runner module (extend self) with 7 public methods
32
+ spec/
33
+ (spec files)
34
+ ```
35
+
36
+ ## Key Constants
37
+
38
+ ```ruby
39
+ STAGES = %i[nigredo albedo citrinitas rubedo]
40
+ ELEMENT_TYPES = %i[fire water earth air aether]
41
+ REACTION_TYPES = %i[synthesis decomposition transmutation catalysis dissolution]
42
+ CATALYST_MULTIPLIER = 3.0 # catalyst reactions multiply purity/potency gain by 3x
43
+ PURITY_LABELS = {
44
+ (0.9..) => :refined, (0.7...0.9) => :purified, (0.5...0.7) => :mixed,
45
+ (0.3...0.5) => :impure, (..0.3) => :crude
46
+ }
47
+ POTENCY_LABELS = {
48
+ (0.85..) => :transcendent, (0.65...0.85) => :potent, (0.45...0.65) => :moderate,
49
+ (0.25...0.45) => :weak, (..0.25) => :inert
50
+ }
51
+ STAGE_LABELS = {
52
+ nigredo: :decomposition, albedo: :purification,
53
+ citrinitas: :illumination, rubedo: :completion
54
+ }
55
+ ```
56
+
57
+ ## Runners
58
+
59
+ ### `Runners::CognitiveAlchemy`
60
+
61
+ Uses `extend self` — methods are module-level, not instance-delegating. Delegates to a per-call `engine` (parameter-injected `Helpers::AlchemyEngine` instance via `engine:` keyword, defaulting to a shared `@engine ||=`).
62
+
63
+ - `create_substance(name:, element_type:, domain: :general, engine: @engine)` — create a new alchemical substance at `:nigredo` stage with default purity/potency
64
+ - `advance_stage(substance_id:, engine: @engine)` — move substance to the next STAGES entry; no-op at `:rubedo`
65
+ - `catalyze(substance_id:, catalyst:, engine: @engine)` — apply a catalyst to a substance, multiplying its purity and potency gain by `CATALYST_MULTIPLIER`
66
+ - `react(substance_id:, reagent_id:, reaction_type:, engine: @engine)` — apply a typed reaction between two substances; returns reaction outcome hash
67
+ - `list_substances(engine: @engine)` — all substances as array of hashes
68
+ - `alchemy_status(engine: @engine)` — summary stats: substance count, stage distribution, average purity/potency
69
+
70
+ ## Helpers
71
+
72
+ ### `Helpers::AlchemyEngine`
73
+ Core engine managing `@substances` hash keyed by ID. `advance_stage` cycles through `STAGES` array index. `catalyze` applies `CATALYST_MULTIPLIER` to substance attribute update. `react` looks up both substances, applies reaction effects based on `reaction_type`, and returns outcome. `substance_list` returns all substances as serialized hashes.
74
+
75
+ ### `Helpers::Substance`
76
+ Value object: name, element_type, domain, stage (starts at `:nigredo`), purity (0.0–1.0), potency (0.0–1.0), reaction_count. `advance!` bumps stage via STAGES index. `refine!(amount)` increases purity clamped to 1.0. `empower!(amount)` increases potency clamped to 1.0. `purity_label` and `potency_label` map to human-readable labels.
77
+
78
+ ## Integration Points
79
+
80
+ No actor defined — no automatic decay or scanning. This is a transformation metaphor: raw observations enter at `:nigredo` and move through stages as evidence accumulates. Pairs with lex-causal-reasoning (causal chains can trigger stage advancement) and lex-abductive-reasoning (best explanations emerge at `:rubedo`). The CATALYST_MULTIPLIER (3x) models insight events — moments where a catalyst dramatically accelerates refinement.
81
+
82
+ ## Development Notes
83
+
84
+ - `extend self` pattern means the runner is a module with module-level methods — no `@engine` instance variable in a class; shared state lives in the module's singleton
85
+ - `advance_stage` is a no-op when substance is already at `:rubedo` — callers should check stage before calling
86
+ - `CATALYST_MULTIPLIER = 3.0` is the only numeric constant that differs from the standard 0.1/0.15 rates used by other extensions
87
+ - Stage ordering is fixed by the `STAGES` array index; array order defines the progression
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'rspec', '~> 3.13'
9
+ gem 'rubocop', '~> 1.75'
10
+ gem 'rubocop-rspec'
11
+ end
12
+
13
+ gem 'legion-gaia', path: '../../legion-gaia'
data/Gemfile.lock ADDED
@@ -0,0 +1,78 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lex-cognitive-alchemy (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.8.9)
10
+ public_suffix (>= 2.0.2, < 8.0)
11
+ ast (2.4.3)
12
+ bigdecimal (4.0.1)
13
+ diff-lcs (1.6.2)
14
+ json (2.19.1)
15
+ json-schema (6.2.0)
16
+ addressable (~> 2.8)
17
+ bigdecimal (>= 3.1, < 5)
18
+ language_server-protocol (3.17.0.5)
19
+ lint_roller (1.1.0)
20
+ mcp (0.8.0)
21
+ json-schema (>= 4.1)
22
+ parallel (1.27.0)
23
+ parser (3.3.10.2)
24
+ ast (~> 2.4.1)
25
+ racc
26
+ prism (1.9.0)
27
+ public_suffix (7.0.5)
28
+ racc (1.8.1)
29
+ rainbow (3.1.1)
30
+ regexp_parser (2.11.3)
31
+ rspec (3.13.2)
32
+ rspec-core (~> 3.13.0)
33
+ rspec-expectations (~> 3.13.0)
34
+ rspec-mocks (~> 3.13.0)
35
+ rspec-core (3.13.6)
36
+ rspec-support (~> 3.13.0)
37
+ rspec-expectations (3.13.5)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.13.0)
40
+ rspec-mocks (3.13.8)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.13.0)
43
+ rspec-support (3.13.7)
44
+ rubocop (1.85.1)
45
+ json (~> 2.3)
46
+ language_server-protocol (~> 3.17.0.2)
47
+ lint_roller (~> 1.1.0)
48
+ mcp (~> 0.6)
49
+ parallel (~> 1.10)
50
+ parser (>= 3.3.0.2)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ regexp_parser (>= 2.9.3, < 3.0)
53
+ rubocop-ast (>= 1.49.0, < 2.0)
54
+ ruby-progressbar (~> 1.7)
55
+ unicode-display_width (>= 2.4.0, < 4.0)
56
+ rubocop-ast (1.49.1)
57
+ parser (>= 3.3.7.2)
58
+ prism (~> 1.7)
59
+ rubocop-rspec (3.9.0)
60
+ lint_roller (~> 1.1)
61
+ rubocop (~> 1.81)
62
+ ruby-progressbar (1.13.0)
63
+ unicode-display_width (3.2.0)
64
+ unicode-emoji (~> 4.1)
65
+ unicode-emoji (4.2.0)
66
+
67
+ PLATFORMS
68
+ arm64-darwin-25
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ lex-cognitive-alchemy!
73
+ rspec (~> 3.13)
74
+ rubocop (~> 1.75)
75
+ rubocop-rspec
76
+
77
+ BUNDLED WITH
78
+ 2.6.9
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # lex-cognitive-alchemy
2
+
3
+ Alchemical metaphor for cognitive transformation — substances move through Jungian stages via reactions and catalysts.
4
+
5
+ ## What It Does
6
+
7
+ Models knowledge refinement as an alchemical process. Cognitive substances (ideas, insights, hypotheses) are created at the crude `:nigredo` stage and purified through four stages: nigredo (decomposition), albedo (purification), citrinitas (illumination), rubedo (completion). Reactions between substances transform them; catalysts multiply the gain by 3x.
8
+
9
+ ## Core Concept: Stage Progression
10
+
11
+ ```
12
+ nigredo -> albedo -> citrinitas -> rubedo
13
+ (crude) (purified) (illuminated) (complete)
14
+ ```
15
+
16
+ Each substance has `purity` (0.0–1.0, crude to refined) and `potency` (0.0–1.0, inert to transcendent). Stage advancement and reactions increase both.
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ client = Legion::Extensions::CognitiveAlchemy::Client.new
22
+
23
+ # Create substances from raw observations
24
+ idea = client.create_substance(name: :cache_invalidation_theory, element_type: :fire, domain: :infrastructure)
25
+ evidence = client.create_substance(name: :production_trace_data, element_type: :earth, domain: :infrastructure)
26
+
27
+ # Apply a reaction between substances
28
+ client.react(
29
+ substance_id: idea[:substance][:id],
30
+ reagent_id: evidence[:substance][:id],
31
+ reaction_type: :synthesis
32
+ )
33
+
34
+ # A breakthrough insight acts as a catalyst (3x multiplier)
35
+ client.catalyze(substance_id: idea[:substance][:id], catalyst: :root_cause_confirmed)
36
+
37
+ # Advance to next stage when ready
38
+ client.advance_stage(substance_id: idea[:substance][:id])
39
+ # substance moves from :nigredo -> :albedo
40
+
41
+ # Check overall transformation progress
42
+ client.alchemy_status
43
+ # => { substance_count: 2, stage_distribution: { nigredo: 1, albedo: 1, ... }, avg_purity: 0.62 }
44
+ ```
45
+
46
+ ## Integration
47
+
48
+ Pairs with lex-causal-reasoning (causal chains can trigger stage advancement as evidence accumulates) and lex-abductive-reasoning (best explanations correspond to `:rubedo` substances — fully refined and potent).
49
+
50
+ ## Development
51
+
52
+ ```bash
53
+ bundle install
54
+ bundle exec rspec
55
+ bundle exec rubocop
56
+ ```
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_alchemy/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-alchemy'
7
+ spec.version = Legion::Extensions::CognitiveAlchemy::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+ spec.license = 'MIT'
11
+
12
+ spec.summary = 'Cognitive alchemy LEX — transmutation of ideas through alchemical stages'
13
+ spec.description = 'Models the Magnum Opus of idea transmutation: substances pass through ' \
14
+ 'nigredo/albedo/citrinitas/rubedo stages, crucible reactions combine elements ' \
15
+ 'under heat and pressure, and philosopher stone catalysis accelerates breakthroughs.'
16
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-alchemy'
17
+
18
+ spec.required_ruby_version = '>= 3.4'
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+ spec.metadata['documentation_uri'] = "#{spec.homepage}#readme"
23
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
24
+ spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
25
+ spec.metadata['rubygems_mfa_required'] = 'true'
26
+
27
+ spec.files = Dir.chdir(__dir__) do
28
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(test|spec|features)/}) }
29
+ end
30
+
31
+ spec.require_paths = ['lib']
32
+ spec.add_development_dependency 'legion-gaia'
33
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ class Client
7
+ include Runners::CognitiveAlchemy
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ module Helpers
7
+ class AlchemyEngine
8
+ def initialize
9
+ @substances = {}
10
+ @reactions = {}
11
+ end
12
+
13
+ def create_substance(element_type:, domain:, content:, purity: nil, potency: nil)
14
+ raise ArgumentError, 'athanor full' if @substances.size >= Constants::MAX_SUBSTANCES
15
+
16
+ sub = Substance.new(element_type: element_type, domain: domain,
17
+ content: content, purity: purity, potency: potency)
18
+ @substances[sub.id] = sub
19
+ sub
20
+ end
21
+
22
+ def advance_stage(substance_id:)
23
+ fetch_substance(substance_id).advance_stage!
24
+ end
25
+
26
+ def catalyze(substance_id:, multiplier: Constants::CATALYST_MULTIPLIER)
27
+ fetch_substance(substance_id).catalyze!(multiplier: multiplier)
28
+ end
29
+
30
+ def react(reaction_type:, input_ids:, heat: 0.5, pressure: 0.5)
31
+ raise ArgumentError, 'too many reactions' if @reactions.size >= Constants::MAX_REACTIONS
32
+
33
+ inputs = input_ids.map { |id| fetch_substance(id) }
34
+ crucible = Crucible.new(reaction_type: reaction_type, input_ids: input_ids,
35
+ heat: heat, pressure: pressure)
36
+
37
+ output = synthesize(inputs, crucible)
38
+ @substances[output.id] = output
39
+ crucible.complete!(output.id)
40
+ @reactions[crucible.id] = crucible
41
+
42
+ { crucible: crucible, output: output }
43
+ end
44
+
45
+ def decay_all!(rate: Constants::SUBSTANCE_DECAY)
46
+ @substances.each_value { |s| s.decay!(rate: rate) }
47
+ pruned = @substances.select { |_, s| s.inert? }.keys
48
+ pruned.each { |id| @substances.delete(id) }
49
+ { decayed: @substances.size, pruned: pruned.size }
50
+ end
51
+
52
+ def substances_by_stage
53
+ counts = Constants::STAGES.to_h { |s| [s, 0] }
54
+ @substances.each_value { |s| counts[s.stage] += 1 }
55
+ counts
56
+ end
57
+
58
+ def substances_by_type
59
+ counts = Constants::ELEMENT_TYPES.to_h { |t| [t, 0] }
60
+ @substances.each_value { |s| counts[s.element_type] += 1 }
61
+ counts
62
+ end
63
+
64
+ def gold_count
65
+ @substances.count { |_, s| s.gold? }
66
+ end
67
+
68
+ def prima_materia_count
69
+ @substances.count { |_, s| s.prima_materia? }
70
+ end
71
+
72
+ def purest(limit: 5)
73
+ @substances.values.sort_by { |s| -s.purity }.first(limit)
74
+ end
75
+
76
+ def most_potent(limit: 5)
77
+ @substances.values.sort_by { |s| -s.potency }.first(limit)
78
+ end
79
+
80
+ def alchemy_report
81
+ {
82
+ total_substances: @substances.size,
83
+ total_reactions: @reactions.size,
84
+ by_stage: substances_by_stage,
85
+ by_type: substances_by_type,
86
+ gold_count: gold_count,
87
+ prima_materia: prima_materia_count,
88
+ avg_purity: avg_metric(:purity),
89
+ avg_potency: avg_metric(:potency)
90
+ }
91
+ end
92
+
93
+ def all_substances
94
+ @substances.values
95
+ end
96
+
97
+ def all_reactions
98
+ @reactions.values
99
+ end
100
+
101
+ private
102
+
103
+ def fetch_substance(id)
104
+ @substances.fetch(id) do
105
+ raise ArgumentError, "substance not found: #{id}"
106
+ end
107
+ end
108
+
109
+ def synthesize(inputs, crucible)
110
+ avg_purity = inputs.sum(&:purity) / inputs.size
111
+ avg_potency = inputs.sum(&:potency) / inputs.size
112
+ boost = crucible.intensity * Constants::TRANSMUTATION_RATE
113
+
114
+ create_substance(
115
+ element_type: inputs.first.element_type,
116
+ domain: inputs.first.domain,
117
+ content: "synthesis(#{inputs.map(&:content).join(' + ')})",
118
+ purity: (avg_purity + boost).clamp(0.0, 1.0),
119
+ potency: (avg_potency + (boost * 0.5)).clamp(0.0, 1.0)
120
+ )
121
+ end
122
+
123
+ def avg_metric(attr)
124
+ return 0.0 if @substances.empty?
125
+
126
+ (substances_metric_sum(attr) / @substances.size).round(10)
127
+ end
128
+
129
+ def substances_metric_sum(attr)
130
+ @substances.values.sum(&attr)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ module Helpers
7
+ module Constants
8
+ # Alchemical stages (Magnum Opus)
9
+ STAGES = %i[nigredo albedo citrinitas rubedo].freeze
10
+
11
+ # Transmutation element types
12
+ ELEMENT_TYPES = %i[
13
+ concept belief skill memory emotion
14
+ pattern habit intuition hypothesis
15
+ ].freeze
16
+
17
+ # Crucible reaction types
18
+ REACTION_TYPES = %i[
19
+ calcination dissolution separation conjunction
20
+ fermentation distillation coagulation
21
+ ].freeze
22
+
23
+ # Maximum substances in the athanor
24
+ MAX_SUBSTANCES = 200
25
+
26
+ # Maximum crucible reactions
27
+ MAX_REACTIONS = 100
28
+
29
+ # Base transmutation rate per stage advance
30
+ TRANSMUTATION_RATE = 0.15
31
+
32
+ # Decay rate for unreacted substances
33
+ SUBSTANCE_DECAY = 0.02
34
+
35
+ # Philosopher's stone catalyst multiplier
36
+ CATALYST_MULTIPLIER = 3.0
37
+
38
+ # Stage labels
39
+ STAGE_LABELS = {
40
+ nigredo: 'Blackening — decomposition of raw material',
41
+ albedo: 'Whitening — purification and washing',
42
+ citrinitas: 'Yellowing — solar awakening',
43
+ rubedo: 'Reddening — final integration and gold'
44
+ }.freeze
45
+
46
+ # Purity labels (range-based)
47
+ PURITY_LABELS = [
48
+ [(0.8..), :aurum],
49
+ [(0.6...0.8), :argentum],
50
+ [(0.4...0.6), :cuprum],
51
+ [(0.2...0.4), :ferrum],
52
+ [(..0.2), :plumbum]
53
+ ].freeze
54
+
55
+ # Potency labels
56
+ POTENCY_LABELS = [
57
+ [(0.8..), :transcendent],
58
+ [(0.6...0.8), :potent],
59
+ [(0.4...0.6), :moderate],
60
+ [(0.2...0.4), :weak],
61
+ [(..0.2), :inert]
62
+ ].freeze
63
+
64
+ def self.label_for(table, value)
65
+ table.each { |range, label| return label if range.cover?(value) }
66
+ table.last.last
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ module Helpers
7
+ class Crucible
8
+ attr_reader :id, :reaction_type, :input_ids, :output_id,
9
+ :heat, :pressure, :started_at, :completed_at
10
+
11
+ def initialize(reaction_type:, input_ids:,
12
+ heat: 0.5, pressure: 0.5)
13
+ validate_reaction!(reaction_type)
14
+ @id = SecureRandom.uuid
15
+ @reaction_type = reaction_type.to_sym
16
+ @input_ids = Array(input_ids).dup
17
+ @output_id = nil
18
+ @heat = heat.to_f.clamp(0.0, 1.0).round(10)
19
+ @pressure = pressure.to_f.clamp(0.0, 1.0).round(10)
20
+ @started_at = Time.now.utc
21
+ @completed_at = nil
22
+ end
23
+
24
+ def complete!(output_id)
25
+ @output_id = output_id
26
+ @completed_at = Time.now.utc
27
+ self
28
+ end
29
+
30
+ def completed?
31
+ !@completed_at.nil?
32
+ end
33
+
34
+ def intensity
35
+ ((@heat + @pressure) / 2.0).round(10)
36
+ end
37
+
38
+ def volatile?
39
+ @heat > 0.8 && @pressure > 0.8
40
+ end
41
+
42
+ def mild?
43
+ intensity < 0.3
44
+ end
45
+
46
+ def to_h
47
+ {
48
+ id: @id,
49
+ reaction_type: @reaction_type,
50
+ input_ids: @input_ids,
51
+ output_id: @output_id,
52
+ heat: @heat,
53
+ pressure: @pressure,
54
+ intensity: intensity,
55
+ volatile: volatile?,
56
+ mild: mild?,
57
+ started_at: @started_at,
58
+ completed_at: @completed_at,
59
+ completed: completed?
60
+ }
61
+ end
62
+
63
+ private
64
+
65
+ def validate_reaction!(val)
66
+ return if Constants::REACTION_TYPES.include?(val.to_sym)
67
+
68
+ raise ArgumentError,
69
+ "unknown reaction type: #{val.inspect}; " \
70
+ "must be one of #{Constants::REACTION_TYPES.inspect}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ module Helpers
7
+ class Substance
8
+ attr_reader :id, :element_type, :domain, :content,
9
+ :stage, :discovered_at, :transmutation_count
10
+ attr_accessor :purity, :potency
11
+
12
+ def initialize(element_type:, domain:, content:,
13
+ purity: nil, potency: nil)
14
+ validate_type!(element_type)
15
+ assign_core(element_type, domain, content)
16
+ assign_metadata(purity, potency)
17
+ end
18
+
19
+ def advance_stage!
20
+ idx = Constants::STAGES.index(@stage)
21
+ return self if idx >= Constants::STAGES.size - 1
22
+
23
+ @stage = Constants::STAGES[idx + 1]
24
+ @purity = (@purity + Constants::TRANSMUTATION_RATE).clamp(0.0, 1.0).round(10)
25
+ @transmutation_count += 1
26
+ self
27
+ end
28
+
29
+ def decay!(rate: Constants::SUBSTANCE_DECAY)
30
+ @potency = (@potency - rate.abs).clamp(0.0, 1.0).round(10)
31
+ self
32
+ end
33
+
34
+ def catalyze!(multiplier: Constants::CATALYST_MULTIPLIER)
35
+ boost = Constants::TRANSMUTATION_RATE * multiplier
36
+ @purity = (@purity + boost).clamp(0.0, 1.0).round(10)
37
+ @potency = (@potency + (boost * 0.5)).clamp(0.0, 1.0).round(10)
38
+ @transmutation_count += 1
39
+ self
40
+ end
41
+
42
+ def prima_materia?
43
+ @stage == :nigredo && @purity < 0.2
44
+ end
45
+
46
+ def gold?
47
+ @stage == :rubedo && @purity >= 0.8
48
+ end
49
+
50
+ def inert?
51
+ @potency < 0.1
52
+ end
53
+
54
+ def purity_label
55
+ Constants.label_for(Constants::PURITY_LABELS, @purity)
56
+ end
57
+
58
+ def potency_label
59
+ Constants.label_for(Constants::POTENCY_LABELS, @potency)
60
+ end
61
+
62
+ def stage_label
63
+ Constants::STAGE_LABELS.fetch(@stage, 'unknown')
64
+ end
65
+
66
+ def to_h
67
+ {
68
+ id: @id,
69
+ element_type: @element_type,
70
+ domain: @domain,
71
+ content: @content,
72
+ stage: @stage,
73
+ stage_label: stage_label,
74
+ purity: @purity,
75
+ purity_label: purity_label,
76
+ potency: @potency,
77
+ potency_label: potency_label,
78
+ transmutation_count: @transmutation_count,
79
+ discovered_at: @discovered_at,
80
+ prima_materia: prima_materia?,
81
+ gold: gold?,
82
+ inert: inert?
83
+ }
84
+ end
85
+
86
+ private
87
+
88
+ def assign_core(element_type, domain, content)
89
+ @id = SecureRandom.uuid
90
+ @element_type = element_type.to_sym
91
+ @domain = domain.to_sym
92
+ @content = content.to_s
93
+ end
94
+
95
+ def assign_metadata(purity, potency)
96
+ @stage = :nigredo
97
+ @purity = (purity || 0.1).to_f.clamp(0.0, 1.0).round(10)
98
+ @potency = (potency || 0.5).to_f.clamp(0.0, 1.0).round(10)
99
+ @transmutation_count = 0
100
+ @discovered_at = Time.now.utc
101
+ end
102
+
103
+ def validate_type!(val)
104
+ return if Constants::ELEMENT_TYPES.include?(val.to_sym)
105
+
106
+ raise ArgumentError,
107
+ "unknown element type: #{val.inspect}; " \
108
+ "must be one of #{Constants::ELEMENT_TYPES.inspect}"
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ module Runners
7
+ module CognitiveAlchemy
8
+ extend self
9
+
10
+ def create_substance(element_type:, domain:, content:,
11
+ purity: nil, potency: nil, engine: nil, **)
12
+ eng = resolve_engine(engine)
13
+ sub = eng.create_substance(element_type: element_type, domain: domain,
14
+ content: content, purity: purity, potency: potency)
15
+ { success: true, substance: sub.to_h }
16
+ rescue ArgumentError => e
17
+ { success: false, error: e.message }
18
+ end
19
+
20
+ def advance_stage(substance_id:, engine: nil, **)
21
+ eng = resolve_engine(engine)
22
+ sub = eng.advance_stage(substance_id: substance_id)
23
+ { success: true, substance: sub.to_h }
24
+ rescue ArgumentError => e
25
+ { success: false, error: e.message }
26
+ end
27
+
28
+ def catalyze(substance_id:, multiplier: nil, engine: nil, **)
29
+ eng = resolve_engine(engine)
30
+ opts = { substance_id: substance_id }
31
+ opts[:multiplier] = multiplier if multiplier
32
+ sub = eng.catalyze(**opts)
33
+ { success: true, substance: sub.to_h }
34
+ rescue ArgumentError => e
35
+ { success: false, error: e.message }
36
+ end
37
+
38
+ def react(reaction_type:, input_ids:, heat: 0.5,
39
+ pressure: 0.5, engine: nil, **)
40
+ eng = resolve_engine(engine)
41
+ result = eng.react(reaction_type: reaction_type, input_ids: input_ids,
42
+ heat: heat, pressure: pressure)
43
+ { success: true, crucible: result[:crucible].to_h,
44
+ output: result[:output].to_h }
45
+ rescue ArgumentError => e
46
+ { success: false, error: e.message }
47
+ end
48
+
49
+ def list_substances(engine: nil, stage: nil, element_type: nil, **)
50
+ eng = resolve_engine(engine)
51
+ results = filter_substances(eng.all_substances,
52
+ stage: stage, element_type: element_type)
53
+ { success: true, substances: results.map(&:to_h),
54
+ count: results.size }
55
+ end
56
+
57
+ def alchemy_status(engine: nil, **)
58
+ eng = resolve_engine(engine)
59
+ { success: true, report: eng.alchemy_report }
60
+ end
61
+
62
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
63
+
64
+ private
65
+
66
+ def filter_substances(substances, stage:, element_type:)
67
+ r = substances
68
+ r = r.select { |s| s.stage == stage.to_sym } if stage
69
+ r = r.select { |s| s.element_type == element_type.to_sym } if element_type
70
+ r
71
+ end
72
+
73
+ def resolve_engine(engine)
74
+ engine || default_engine
75
+ end
76
+
77
+ def default_engine
78
+ @default_engine ||= Helpers::AlchemyEngine.new
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveAlchemy
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ require_relative 'cognitive_alchemy/version'
6
+ require_relative 'cognitive_alchemy/helpers/constants'
7
+ require_relative 'cognitive_alchemy/helpers/substance'
8
+ require_relative 'cognitive_alchemy/helpers/crucible'
9
+ require_relative 'cognitive_alchemy/helpers/alchemy_engine'
10
+ require_relative 'cognitive_alchemy/runners/cognitive_alchemy'
11
+ require_relative 'cognitive_alchemy/client'
12
+
13
+ module Legion
14
+ module Extensions
15
+ module CognitiveAlchemy
16
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-alchemy
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 the Magnum Opus of idea transmutation: substances pass through
27
+ nigredo/albedo/citrinitas/rubedo stages, crucible reactions combine elements under
28
+ heat and pressure, and philosopher stone catalysis accelerates breakthroughs.'
29
+ email:
30
+ - matthewdiverson@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".github/workflows/ci.yml"
36
+ - ".gitignore"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - CLAUDE.md
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - README.md
43
+ - lex-cognitive-alchemy.gemspec
44
+ - lib/legion/extensions/cognitive_alchemy.rb
45
+ - lib/legion/extensions/cognitive_alchemy/client.rb
46
+ - lib/legion/extensions/cognitive_alchemy/helpers/alchemy_engine.rb
47
+ - lib/legion/extensions/cognitive_alchemy/helpers/constants.rb
48
+ - lib/legion/extensions/cognitive_alchemy/helpers/crucible.rb
49
+ - lib/legion/extensions/cognitive_alchemy/helpers/substance.rb
50
+ - lib/legion/extensions/cognitive_alchemy/runners/cognitive_alchemy.rb
51
+ - lib/legion/extensions/cognitive_alchemy/version.rb
52
+ homepage: https://github.com/LegionIO/lex-cognitive-alchemy
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-alchemy
57
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-alchemy
58
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-alchemy#readme
59
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-alchemy/blob/main/CHANGELOG.md
60
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-alchemy/issues
61
+ rubygems_mfa_required: 'true'
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.4'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.6.9
77
+ specification_version: 4
78
+ summary: Cognitive alchemy LEX — transmutation of ideas through alchemical stages
79
+ test_files: []