lex-cognitive-quicksilver 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: 571d9e7b2d96df5d5efaa0bc37a1fe7427d7059a128f5b1b6326074447179de2
4
+ data.tar.gz: eb78fdceccf7682d16a9466280b47fd2868a49c0c481f1c318551d9e46df02ac
5
+ SHA512:
6
+ metadata.gz: d95e318ddc47a7402d10108c37b246538cdf5eeba22ae591e006186921089a457f2499dcbfe9bd484bea6633cc7d2d70a242243e5c7f2215383a018eb8f59d01
7
+ data.tar.gz: 6601e6ddc468ef7ae7241411d0d54a55dbb09b9e74244fa092a59f8d23d7e4e1869ad821b67defbf52697dfa57dbdc060bcb53d858a7438f252ea51c32e5969d
@@ -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,4 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
4
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+ SuggestExtensions: false
5
+ Layout/LineLength:
6
+ Max: 160
7
+ Style/Documentation:
8
+ Enabled: false
9
+ Naming/PredicateMethod:
10
+ Enabled: false
11
+ Naming/PredicatePrefix:
12
+ Enabled: false
13
+ Metrics/ClassLength:
14
+ Max: 150
15
+ Metrics/MethodLength:
16
+ Max: 25
17
+ Metrics/AbcSize:
18
+ Max: 25
19
+ Metrics/ParameterLists:
20
+ Max: 8
21
+ MaxOptionalParameters: 8
22
+ Layout/HashAlignment:
23
+ EnforcedColonStyle: table
24
+ EnforcedHashRocketStyle: table
25
+ Metrics/BlockLength:
26
+ Exclude:
27
+ - 'spec/**/*'
28
+ Style/FrozenStringLiteralComment:
29
+ Enabled: true
30
+ Style/OneClassPerFile:
31
+ Exclude:
32
+ - 'spec/spec_helper.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,123 @@
1
+ # lex-cognitive-quicksilver
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-cognitive-quicksilver`
6
+ - **Version**: 0.1.0
7
+ - **Namespace**: `Legion::Extensions::CognitiveQuicksilver`
8
+
9
+ ## Purpose
10
+
11
+ Models cognitive fluidity as a mercury-like substance. Thoughts, impulses, and mental contents are represented as **droplets** that can merge, split, evaporate, be captured, or pool together. The metaphor reflects how cognitive content flows, coalesces, and transforms in states of high versus low mental fluidity.
12
+
13
+ ## Gem Info
14
+
15
+ - **Gemspec**: `lex-cognitive-quicksilver.gemspec`
16
+ - **Require**: `lex-cognitive-quicksilver`
17
+ - **Ruby**: >= 3.4
18
+ - **License**: MIT
19
+ - **Homepage**: https://github.com/LegionIO/lex-cognitive-quicksilver
20
+
21
+ ## File Structure
22
+
23
+ ```
24
+ lib/legion/extensions/cognitive_quicksilver/
25
+ version.rb # VERSION = '0.1.0'
26
+ helpers/
27
+ constants.rb # FORM_TYPES, SURFACE_TYPES, label tables, fluidity constants
28
+ droplet.rb # Droplet class — individual unit of cognitive content
29
+ pool.rb # Pool class — collection of droplets on a surface
30
+ quicksilver_engine.rb # QuicksilverEngine — manages droplets and pools
31
+ runners/
32
+ cognitive_quicksilver.rb # Runner module — public API methods
33
+ client.rb # Client class — instantiates engine, includes runner
34
+ ```
35
+
36
+ ## Key Constants
37
+
38
+ | Constant | Value | Meaning |
39
+ |---|---|---|
40
+ | `FORM_TYPES` | `[:liquid, :droplet, :bead, :stream, :pool]` | Valid droplet forms |
41
+ | `SURFACE_TYPES` | `[:glass, :metal, :wood, :stone, :fabric]` | Valid pool/droplet surfaces |
42
+ | `MAX_DROPLETS` | 500 | Hard cap on tracked droplets |
43
+ | `MAX_POOLS` | 50 | Hard cap on tracked pools |
44
+ | `FLUIDITY_BASE` | 0.8 | Default fluidity for new droplets |
45
+ | `SURFACE_TENSION` | 0.3 | Default pool surface tension |
46
+ | `EVAPORATION_RATE` | 0.02 | Mass reduction per `evaporate!` call |
47
+ | `COALESCENCE_BONUS` | 0.1 | Extra mass added when two droplets merge |
48
+
49
+ ## Key Classes
50
+
51
+ ### `Helpers::Droplet`
52
+
53
+ An individual unit of cognitive content with form, mass, fluidity, and surface properties.
54
+
55
+ - `shift_form!(new_form)` — changes form and adjusts fluidity to `FORM_FLUIDITY[form]`
56
+ - `merge!(other_droplet)` — absorbs another droplet; mass sums + coalescence bonus; fluidity averages
57
+ - `split!` — halves mass and spawns twin; returns nil if mass <= 0.2
58
+ - `capture!` / `release!` — halves/doubles fluidity; toggles `@captured` flag
59
+ - `evaporate!` — reduces mass by `EVAPORATION_RATE`
60
+ - `elusive?` — fluidity >= 0.7 and not captured
61
+ - `stable?` — fluidity < 0.4 or captured
62
+ - `vanishing?` — mass < 0.1
63
+
64
+ ### `Helpers::Pool`
65
+
66
+ A surface that holds droplets.
67
+
68
+ - `add_droplet(id)` / `remove_droplet(id)` — adjusts depth by `DEPTH_CHANGE`
69
+ - `agitate!` — lowers surface tension, probabilistically releases droplets
70
+ - `settle!` — raises surface tension
71
+ - `reflective?` — depth >= 0.7 and surface_tension >= 0.5
72
+ - `shallow?` — depth < 0.2
73
+
74
+ ### `Helpers::QuicksilverEngine`
75
+
76
+ Registry and coordinator for droplets and pools.
77
+
78
+ - `create_droplet(form:, content:, **)` — enforces MAX_DROPLETS
79
+ - `create_pool(surface_type:, **)` — enforces MAX_POOLS
80
+ - `shift_form / merge_droplets / split_droplet / capture_droplet / release_droplet`
81
+ - `add_to_pool(droplet_id:, pool_id:)`
82
+ - `agitate_pool(pool_id:)`
83
+ - `evaporate_all!` — evaporates all droplets, removes vanishing ones, returns removed IDs
84
+ - `quicksilver_report` — aggregate counts and averages
85
+
86
+ ## Runners
87
+
88
+ Module: `Legion::Extensions::CognitiveQuicksilver::Runners::CognitiveQuicksilver`
89
+
90
+ | Runner | Key Args | Returns |
91
+ |---|---|---|
92
+ | `create_droplet` | `form:`, `content:` | `{ success:, droplet: }` |
93
+ | `create_pool` | `surface_type:` | `{ success:, pool: }` |
94
+ | `shift_form` | `droplet_id:`, `new_form:` | `{ success:, droplet: }` |
95
+ | `merge` | `droplet_a_id:`, `droplet_b_id:` | `{ success:, droplet: }` |
96
+ | `split` | `droplet_id:` | `{ success:, original:, twin: }` or `{ success: false, error: }` |
97
+ | `capture` | `droplet_id:` | `{ success:, droplet: }` |
98
+ | `release` | `droplet_id:` | `{ success:, droplet: }` |
99
+ | `add_to_pool` | `droplet_id:`, `pool_id:` | `{ success:, pool: }` |
100
+ | `list_droplets` | — | `{ success:, droplets:, count: }` |
101
+ | `quicksilver_status` | — | aggregate report hash |
102
+
103
+ All runners accept optional `engine:` keyword to inject a test engine instance.
104
+
105
+ ## Helpers
106
+
107
+ - `Constants#label_for(table, value)` — range-table lookup for `COHESION_LABELS` and `FLUIDITY_LABELS`
108
+ - `Droplet#cohesion_label` / `#fluidity_label` — delegates to `Constants.label_for`
109
+
110
+ ## Integration Points
111
+
112
+ - No direct dependencies on other LegionIO extensions
113
+ - Designed as a standalone cognitive metaphor subsystem
114
+ - Can be wired into `lex-tick` phase handlers via `lex-cortex` if a fluidity signal is needed
115
+ - All state is in-memory per `QuicksilverEngine` instance; no persistence
116
+
117
+ ## Development Notes
118
+
119
+ - The `engine:` keyword in each runner method allows test injection without mocking global state
120
+ - `split!` returns `nil` (not an error) when mass <= 0.2; runner handles this as `{ success: false, error: }`
121
+ - `merge!` deletes the absorbed droplet from the engine registry; only one object remains
122
+ - `evaporate_all!` is a batch operation; individual evaporation is only through direct Droplet calls
123
+ - No actors defined; this extension is driven entirely by external calls or task triggers
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,67 @@
1
+ # lex-cognitive-quicksilver
2
+
3
+ A LegionIO cognitive architecture extension that models mental fluidity using a mercury metaphor. Cognitive content flows, coalesces, splits, and evaporates as droplets of varying form and mass.
4
+
5
+ ## What It Does
6
+
7
+ Represents thought-units as **droplets** moving across cognitive surfaces. Droplets have:
8
+
9
+ - **Form**: liquid, droplet, bead, stream, or pool — each with different fluidity characteristics
10
+ - **Mass**: accumulates through merging, depletes through evaporation
11
+ - **Fluidity**: determines how easily the droplet moves; capture reduces fluidity, release restores it
12
+
13
+ Droplets can collect into **pools** on surfaces (glass, metal, wood, stone, fabric). Pools track depth and surface tension; agitating a pool releases some droplets.
14
+
15
+ This extension provides a low-level building block for modeling states of mental flow, dissociation, and cognitive cohesion.
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ require 'lex-cognitive-quicksilver'
21
+
22
+ client = Legion::Extensions::CognitiveQuicksilver::Client.new
23
+
24
+ # Create a droplet
25
+ result = client.create_droplet(form: :droplet, content: 'working memory fragment')
26
+ droplet_id = result[:droplet][:id]
27
+
28
+ # Create a pool and add the droplet
29
+ pool_result = client.create_pool(surface_type: :glass)
30
+ client.add_to_pool(droplet_id: droplet_id, pool_id: pool_result[:pool][:id])
31
+
32
+ # Shift form and check state
33
+ client.shift_form(droplet_id: droplet_id, new_form: :stream)
34
+ client.quicksilver_status
35
+ # => { total_droplets: 1, total_pools: 1, elusive_count: 0, ... }
36
+ ```
37
+
38
+ ### Key Operations
39
+
40
+ ```ruby
41
+ # Merge two droplets
42
+ client.merge(droplet_a_id: id1, droplet_b_id: id2)
43
+
44
+ # Split a droplet in half (requires mass > 0.2)
45
+ client.split(droplet_id: id)
46
+
47
+ # Capture a droplet (reduces fluidity)
48
+ client.capture(droplet_id: id)
49
+
50
+ # Release it again
51
+ client.release(droplet_id: id)
52
+
53
+ # List all droplets
54
+ client.list_droplets
55
+ ```
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ bundle install
61
+ bundle exec rspec
62
+ bundle exec rubocop
63
+ ```
64
+
65
+ ## License
66
+
67
+ MIT
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_quicksilver/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-quicksilver'
7
+ spec.version = Legion::Extensions::CognitiveQuicksilver::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Quicksilver'
12
+ spec.description = 'Cognitive quicksilver fluidity for LegionIO agentic architecture'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
25
+ spec.require_paths = ['lib']
26
+ spec.add_development_dependency 'legion-gaia'
27
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_quicksilver/helpers/constants'
4
+ require 'legion/extensions/cognitive_quicksilver/helpers/droplet'
5
+ require 'legion/extensions/cognitive_quicksilver/helpers/pool'
6
+ require 'legion/extensions/cognitive_quicksilver/helpers/quicksilver_engine'
7
+ require 'legion/extensions/cognitive_quicksilver/runners/cognitive_quicksilver'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitiveQuicksilver
12
+ class Client
13
+ include Runners::CognitiveQuicksilver
14
+
15
+ def initialize
16
+ @quicksilver_engine = Helpers::QuicksilverEngine.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :quicksilver_engine
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveQuicksilver
6
+ module Helpers
7
+ module Constants
8
+ FORM_TYPES = %i[liquid droplet bead stream pool].freeze
9
+ SURFACE_TYPES = %i[glass metal wood stone fabric].freeze
10
+
11
+ MAX_DROPLETS = 500
12
+ MAX_POOLS = 50
13
+
14
+ FLUIDITY_BASE = 0.8
15
+ SURFACE_TENSION = 0.3
16
+ EVAPORATION_RATE = 0.02
17
+ COALESCENCE_BONUS = 0.1
18
+
19
+ COHESION_LABELS = [
20
+ { range: (0.8..1.0), label: :unified },
21
+ { range: (0.6...0.8), label: :cohesive },
22
+ { range: (0.4...0.6), label: :scattered },
23
+ { range: (0.2...0.4), label: :dispersed },
24
+ { range: (0.0...0.2), label: :atomized }
25
+ ].freeze
26
+
27
+ FLUIDITY_LABELS = [
28
+ { range: (0.8..1.0), label: :liquid },
29
+ { range: (0.6...0.8), label: :flowing },
30
+ { range: (0.4...0.6), label: :viscous },
31
+ { range: (0.2...0.4), label: :sluggish },
32
+ { range: (0.0...0.2), label: :solid }
33
+ ].freeze
34
+
35
+ module_function
36
+
37
+ def label_for(table, value)
38
+ clamped = value.clamp(0.0, 1.0)
39
+ entry = table.find { |row| row[:range].include?(clamped) }
40
+ entry ? entry[:label] : table.last[:label]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveQuicksilver
8
+ module Helpers
9
+ class Droplet
10
+ attr_reader :id, :form, :content, :mass, :fluidity, :surface, :captured, :created_at
11
+
12
+ FORM_FLUIDITY = {
13
+ liquid: 0.9,
14
+ droplet: 0.8,
15
+ bead: 0.7,
16
+ stream: 0.6,
17
+ pool: 0.3
18
+ }.freeze
19
+
20
+ def initialize(form:, content:, mass: 0.3, fluidity: Constants::FLUIDITY_BASE, surface: :glass)
21
+ raise ArgumentError, "invalid form: #{form}" unless Constants::FORM_TYPES.include?(form)
22
+ raise ArgumentError, "invalid surface: #{surface}" unless Constants::SURFACE_TYPES.include?(surface)
23
+
24
+ @id = SecureRandom.uuid
25
+ @form = form
26
+ @content = content
27
+ @mass = mass.clamp(0.0, 1.0)
28
+ @fluidity = fluidity.clamp(0.0, 1.0)
29
+ @surface = surface
30
+ @captured = false
31
+ @created_at = Time.now.utc
32
+ end
33
+
34
+ def shift_form!(new_form)
35
+ raise ArgumentError, "invalid form: #{new_form}" unless Constants::FORM_TYPES.include?(new_form)
36
+
37
+ @form = new_form
38
+ @fluidity = FORM_FLUIDITY.fetch(new_form, Constants::FLUIDITY_BASE).clamp(0.0, 1.0)
39
+ self
40
+ end
41
+
42
+ def merge!(other_droplet)
43
+ @mass = (@mass + other_droplet.mass + Constants::COALESCENCE_BONUS).clamp(0.0, 1.0)
44
+ @form = @mass >= other_droplet.mass ? @form : other_droplet.form
45
+ @fluidity = ((@fluidity + other_droplet.fluidity) / 2.0).round(10)
46
+ self
47
+ end
48
+
49
+ def split!
50
+ return nil if @mass <= 0.2
51
+
52
+ half_mass = (@mass / 2.0).round(10)
53
+ @mass = half_mass
54
+
55
+ self.class.new(
56
+ form: @form,
57
+ content: @content,
58
+ mass: half_mass,
59
+ fluidity: @fluidity,
60
+ surface: @surface
61
+ )
62
+ end
63
+
64
+ def capture!
65
+ @captured = true
66
+ @fluidity = (@fluidity / 2.0).round(10)
67
+ self
68
+ end
69
+
70
+ def release!
71
+ @captured = false
72
+ @fluidity = (@fluidity * 2.0).clamp(0.0, 1.0)
73
+ self
74
+ end
75
+
76
+ def evaporate!
77
+ @mass = (@mass - Constants::EVAPORATION_RATE).clamp(0.0, 1.0).round(10)
78
+ self
79
+ end
80
+
81
+ def elusive?
82
+ @fluidity >= 0.7 && !@captured
83
+ end
84
+
85
+ def stable?
86
+ @fluidity < 0.4 || @captured
87
+ end
88
+
89
+ def vanishing?
90
+ @mass < 0.1
91
+ end
92
+
93
+ def cohesion_label
94
+ Constants.label_for(Constants::COHESION_LABELS, @mass)
95
+ end
96
+
97
+ def fluidity_label
98
+ Constants.label_for(Constants::FLUIDITY_LABELS, @fluidity)
99
+ end
100
+
101
+ def to_h
102
+ {
103
+ id: @id,
104
+ form: @form,
105
+ content: @content,
106
+ mass: @mass.round(10),
107
+ fluidity: @fluidity.round(10),
108
+ surface: @surface,
109
+ captured: @captured,
110
+ elusive: elusive?,
111
+ stable: stable?,
112
+ vanishing: vanishing?,
113
+ cohesion: cohesion_label,
114
+ fluidity_lbl: fluidity_label,
115
+ created_at: @created_at
116
+ }
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveQuicksilver
8
+ module Helpers
9
+ class Pool
10
+ attr_reader :id, :surface_type, :depth, :droplet_ids, :surface_tension, :created_at
11
+
12
+ DEPTH_CHANGE = 0.05
13
+ TENSION_DECREASE = 0.1
14
+ TENSION_INCREASE = 0.05
15
+
16
+ def initialize(surface_type:, depth: 0.5, surface_tension: Constants::SURFACE_TENSION)
17
+ raise ArgumentError, "invalid surface_type: #{surface_type}" unless Constants::SURFACE_TYPES.include?(surface_type)
18
+
19
+ @id = SecureRandom.uuid
20
+ @surface_type = surface_type
21
+ @depth = depth.clamp(0.0, 1.0)
22
+ @droplet_ids = []
23
+ @surface_tension = surface_tension.clamp(0.0, 1.0)
24
+ @created_at = Time.now.utc
25
+ end
26
+
27
+ def add_droplet(droplet_id)
28
+ @droplet_ids << droplet_id unless @droplet_ids.include?(droplet_id)
29
+ @depth = (@depth + DEPTH_CHANGE).clamp(0.0, 1.0).round(10)
30
+ self
31
+ end
32
+
33
+ def remove_droplet(droplet_id)
34
+ @droplet_ids.delete(droplet_id)
35
+ @depth = (@depth - DEPTH_CHANGE).clamp(0.0, 1.0).round(10)
36
+ self
37
+ end
38
+
39
+ def agitate!
40
+ @surface_tension = (@surface_tension - TENSION_DECREASE).clamp(0.0, 1.0).round(10)
41
+ released = []
42
+ @droplet_ids.each do |did|
43
+ released << did if rand > @surface_tension
44
+ end
45
+ released.each { |did| remove_droplet(did) }
46
+ released
47
+ end
48
+
49
+ def settle!
50
+ @surface_tension = (@surface_tension + TENSION_INCREASE).clamp(0.0, 1.0).round(10)
51
+ self
52
+ end
53
+
54
+ def reflective?
55
+ @depth >= 0.7 && @surface_tension >= 0.5
56
+ end
57
+
58
+ def shallow?
59
+ @depth < 0.2
60
+ end
61
+
62
+ def to_h
63
+ {
64
+ id: @id,
65
+ surface_type: @surface_type,
66
+ depth: @depth.round(10),
67
+ droplet_ids: @droplet_ids.dup,
68
+ droplet_count: @droplet_ids.size,
69
+ surface_tension: @surface_tension.round(10),
70
+ reflective: reflective?,
71
+ shallow: shallow?,
72
+ created_at: @created_at
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end