lex-cognitive-tessellation 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 +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +37 -0
- data/CLAUDE.md +127 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +78 -0
- data/README.md +56 -0
- data/lex-cognitive-tessellation.gemspec +29 -0
- data/lib/legion/extensions/cognitive_tessellation/client.rb +15 -0
- data/lib/legion/extensions/cognitive_tessellation/helpers/constants.rb +68 -0
- data/lib/legion/extensions/cognitive_tessellation/helpers/mosaic.rb +95 -0
- data/lib/legion/extensions/cognitive_tessellation/helpers/tessellation_engine.rb +137 -0
- data/lib/legion/extensions/cognitive_tessellation/helpers/tile.rb +72 -0
- data/lib/legion/extensions/cognitive_tessellation/runners/cognitive_tessellation.rb +54 -0
- data/lib/legion/extensions/cognitive_tessellation/version.rb +9 -0
- data/lib/legion/extensions/cognitive_tessellation.rb +18 -0
- data/spec/legion/extensions/cognitive_tessellation/client_spec.rb +40 -0
- data/spec/legion/extensions/cognitive_tessellation/helpers/mosaic_spec.rb +99 -0
- data/spec/legion/extensions/cognitive_tessellation/helpers/tessellation_engine_spec.rb +145 -0
- data/spec/legion/extensions/cognitive_tessellation/helpers/tile_spec.rb +138 -0
- data/spec/spec_helper.rb +28 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e7f6f52f6c2c0a13ef9974ca5dd9d9868fe076e7c1a2ba6a3e23eae1e6c7c610
|
|
4
|
+
data.tar.gz: b70c6b251e6443bc7c13976cbe691709342baf8f373cf396f8f3bec227d58a74
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 593ba1d137e5c15f6d87284963efff612b2118b08d53b339cf6308f43c07178df2a8e49a98ad73f96de6235664aded53b0b72bd90a528409f139160c7f26041f
|
|
7
|
+
data.tar.gz: e3cde566be075711228beb30745ab180184125624ad6f3f79997d5af5e8a6332433163fa741116fab81ca265b5c6cb0debe565c7d062e31c42b98f46c028f989
|
|
@@ -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
data/.rspec
ADDED
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,127 @@
|
|
|
1
|
+
# lex-cognitive-tessellation
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-cognitive-tessellation`
|
|
6
|
+
- **Version**: 0.1.0
|
|
7
|
+
- **Namespace**: `Legion::Extensions::CognitiveTessellation`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Models knowledge coverage as a tessellation — a pattern of tiles fitting together without gaps or excessive overlap. Each tile represents a unit of cognitive content (knowledge, skill, pattern, belief, etc.) in a domain. Tiles expand or shrink in coverage, connect to adjacent tiles, and auto-organize into per-domain mosaics. The model surfaces gaps (under-covered tiles), seamless fits (well-integrated tiles), and overall coherence.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Gemspec**: `lex-cognitive-tessellation.gemspec`
|
|
16
|
+
- **Require**: `lex-cognitive-tessellation`
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
- **License**: MIT
|
|
19
|
+
- **Homepage**: https://github.com/LegionIO/lex-cognitive-tessellation
|
|
20
|
+
|
|
21
|
+
## File Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/legion/extensions/cognitive_tessellation/
|
|
25
|
+
version.rb
|
|
26
|
+
helpers/
|
|
27
|
+
constants.rb # Shapes, domains, tile types, coverage/fit/density label tables
|
|
28
|
+
tile.rb # Tile class — one cognitive knowledge unit
|
|
29
|
+
mosaic.rb # Mosaic class — per-domain tile collection
|
|
30
|
+
tessellation_engine.rb # TessellationEngine — registry and aggregate metrics
|
|
31
|
+
runners/
|
|
32
|
+
cognitive_tessellation.rb # Runner module — public API
|
|
33
|
+
client.rb
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Key Constants
|
|
37
|
+
|
|
38
|
+
| Constant | Value | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `MAX_TILES` | 500 | Hard cap; lowest effective-coverage tiles pruned when exceeded |
|
|
41
|
+
| `MAX_MOSAICS` | 50 | Max per-domain mosaics (not enforced in engine) |
|
|
42
|
+
| `FULL_COVERAGE_THRESHOLD` | 0.95 | Coverage >= this = `full_coverage?` true |
|
|
43
|
+
| `GAP_THRESHOLD` | 0.3 | Fit score < this = `gapped?` true |
|
|
44
|
+
| `OVERLAP_THRESHOLD` | 0.7 | Fit + many adjacents triggers `overlapping?` |
|
|
45
|
+
| `FIT_TOLERANCE` | 0.05 | Reference tolerance (defined, not used) |
|
|
46
|
+
| `COVERAGE_GROWTH` | 0.08 | Coverage increase per `expand!` |
|
|
47
|
+
| `COVERAGE_DECAY` | 0.02 | Coverage decrease per `shrink!` |
|
|
48
|
+
| `OVERLAP_PENALTY` | 0.05 | Per-adjacent penalty when `overlapping?` |
|
|
49
|
+
|
|
50
|
+
`TILE_SHAPES`: `[:triangular, :square, :hexagonal, :pentagonal, :irregular, :fractal, :amorphous, :crystalline]`
|
|
51
|
+
|
|
52
|
+
`DOMAINS`: `[:cognitive, :emotional, :procedural, :semantic, :episodic, :social, :creative, :analytical]`
|
|
53
|
+
|
|
54
|
+
`TILE_TYPES`: `[:knowledge, :skill, :pattern, :belief, :heuristic, :schema, :model, :framework]`
|
|
55
|
+
|
|
56
|
+
Coverage labels: `0.9+` = `:complete`, `0.7..0.9` = `:substantial`, `0.5..0.7` = `:partial`, `0.3..0.5` = `:sparse`, `<0.3` = `:fragmented`
|
|
57
|
+
|
|
58
|
+
Fit labels: `0.8+` = `:seamless`, `0.6..0.8` = `:snug`, `0.4..0.6` = `:loose`, `0.2..0.4` = `:gapped`, `<0.2` = `:disjointed`
|
|
59
|
+
|
|
60
|
+
## Key Classes
|
|
61
|
+
|
|
62
|
+
### `Helpers::Tile`
|
|
63
|
+
|
|
64
|
+
One cognitive knowledge unit.
|
|
65
|
+
|
|
66
|
+
- `expand!(amount)` — increases coverage by amount (default `COVERAGE_GROWTH`)
|
|
67
|
+
- `shrink!(amount)` — decreases coverage by amount (default `COVERAGE_DECAY`)
|
|
68
|
+
- `adjust_fit!(new_fit)` — sets fit score
|
|
69
|
+
- `connect!(other_id)` — adds to `adjacent_ids` (no duplicates)
|
|
70
|
+
- `full_coverage?` — coverage >= 0.95; `gapped?` — fit_score < 0.3; `seamless?` — fit_score >= 0.8
|
|
71
|
+
- `overlapping?` — fit_score > 0.7 AND adjacent_ids.size > 3
|
|
72
|
+
- `isolated?` — no adjacent tiles; `well_connected?` — 3+ adjacent tiles
|
|
73
|
+
- `effective_coverage` — coverage minus overlap penalties
|
|
74
|
+
|
|
75
|
+
### `Helpers::Mosaic`
|
|
76
|
+
|
|
77
|
+
A per-domain collection of tiles.
|
|
78
|
+
|
|
79
|
+
- `add_tile(tile)` — appends and auto-connects to last 2 tiles in same domain via `compute_fit`
|
|
80
|
+
- `total_coverage` — mean effective_coverage across tiles
|
|
81
|
+
- `coherence` — ratio of well-connected tiles (adjacency >= 3)
|
|
82
|
+
- `uniformity` — `1 - sqrt(variance_of_coverages)`; high = tiles are evenly covered
|
|
83
|
+
- `complete?` — total_coverage >= `FULL_COVERAGE_THRESHOLD`
|
|
84
|
+
- `auto_connect` computes fit: `0.5 + shape_match(0.2 if same) + type_match(0.1 if same) + rand(0..0.2)`
|
|
85
|
+
|
|
86
|
+
### `Helpers::TessellationEngine`
|
|
87
|
+
|
|
88
|
+
Registry for all tiles and mosaics.
|
|
89
|
+
|
|
90
|
+
- `create_tile(tile_type:, shape:, domain:, coverage:, fit_score:)` — auto-creates mosaic for domain; prunes lowest-coverage if over `MAX_TILES`
|
|
91
|
+
- `expand_tile(tile_id:, amount:)` — delegates to tile
|
|
92
|
+
- `shrink_all!` — shrinks every tile by `COVERAGE_DECAY`
|
|
93
|
+
- `connect_tiles(tile_a_id:, tile_b_id:)` — bidirectional adjacency
|
|
94
|
+
- `gapped_tiles` / `seamless_tiles` / `isolated_tiles` / `full_coverage_tiles` — filtered tile lists
|
|
95
|
+
- `overall_coverage` / `overall_fit` / `overall_coherence` — aggregate scores
|
|
96
|
+
- `gap_density` — ratio of gapped tiles
|
|
97
|
+
- `tessellation_report` — full report with all counts and labels
|
|
98
|
+
|
|
99
|
+
## Runners
|
|
100
|
+
|
|
101
|
+
Module: `Legion::Extensions::CognitiveTessellation::Runners::CognitiveTessellation`
|
|
102
|
+
|
|
103
|
+
| Runner | Key Args | Returns |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `create_tile` | `tile_type:`, `shape:`, `domain:`, `coverage:`, `fit_score:` | `{ success:, tile: }` |
|
|
106
|
+
| `expand_tile` | `tile_id:`, `amount:` | `{ success:, tile: }` or `{ success: false, error: }` |
|
|
107
|
+
| `connect_tiles` | `tile_a_id:`, `tile_b_id:` | `{ success:, connected:, tile_a:, tile_b: }` or error |
|
|
108
|
+
| `shrink_all` | — | `{ success: true }` |
|
|
109
|
+
| `list_gaps` | — | `{ success:, count:, tiles: }` |
|
|
110
|
+
| `tessellation_status` | — | `{ success:, total_tiles:, overall_coverage:, gap_density:, mosaics:, ... }` |
|
|
111
|
+
|
|
112
|
+
Note: the runner uses `@default_engine` directly (not `engine || @default_engine` for all runners — `create_tile` uses the pattern but some others don't initialize it lazily).
|
|
113
|
+
|
|
114
|
+
## Integration Points
|
|
115
|
+
|
|
116
|
+
- No actors defined; `shrink_all` should be called periodically as a decay tick
|
|
117
|
+
- `list_gaps` identifies knowledge domains needing expansion — can drive learning prioritization
|
|
118
|
+
- Mosaics are auto-created per domain when the first tile for a domain is created
|
|
119
|
+
- All state is in-memory per `TessellationEngine` instance
|
|
120
|
+
|
|
121
|
+
## Development Notes
|
|
122
|
+
|
|
123
|
+
- The runner accesses `@default_engine` directly without the `||=` pattern in some methods — the engine must be initialized before those runners are called (via `create_tile` or another `||=` path)
|
|
124
|
+
- `compute_fit` in Mosaic includes `rand * 0.2` — fit scores have a random component introduced at tile connection time
|
|
125
|
+
- `overlapping?` requires BOTH high fit score AND more than 3 adjacents — fit score alone is not sufficient
|
|
126
|
+
- `auto_connect` connects new tiles to the last 2 tiles in the same domain only; tiles added to the same-domain mosaic but not via the engine are not connected
|
|
127
|
+
- `prune_tiles` removes the lowest-effective-coverage tiles when over `MAX_TILES`
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
lex-cognitive-tessellation (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-tessellation!
|
|
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,56 @@
|
|
|
1
|
+
# lex-cognitive-tessellation
|
|
2
|
+
|
|
3
|
+
A LegionIO cognitive architecture extension that models knowledge coverage as a tessellation. Tiles represent cognitive units (knowledge, skills, patterns, beliefs) that fit together across domains, growing through expansion and fading through decay.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Tracks **tiles** of eight types (`knowledge`, `skill`, `pattern`, `belief`, `heuristic`, `schema`, `model`, `framework`) across eight domains (`cognitive`, `emotional`, `procedural`, `semantic`, `episodic`, `social`, `creative`, `analytical`).
|
|
8
|
+
|
|
9
|
+
Each tile has coverage (how much of its domain it explains) and a fit score (how well it integrates with neighbors). Tiles auto-organize into per-domain mosaics when created. The system surfaces gaps, isolated tiles, and coherence across the knowledge landscape.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require 'lex-cognitive-tessellation'
|
|
15
|
+
|
|
16
|
+
client = Legion::Extensions::CognitiveTessellation::Client.new
|
|
17
|
+
|
|
18
|
+
# Create a knowledge tile
|
|
19
|
+
t1 = client.create_tile(tile_type: :knowledge, shape: :hexagonal, domain: :semantic, coverage: 0.4)
|
|
20
|
+
# => { success: true, tile: { id: "uuid...", coverage: 0.4, fit_score: 0.5, gapped: false, seamless: false, ... } }
|
|
21
|
+
|
|
22
|
+
t2 = client.create_tile(tile_type: :schema, shape: :hexagonal, domain: :semantic, coverage: 0.6)
|
|
23
|
+
t3 = client.create_tile(tile_type: :knowledge, shape: :square, domain: :procedural, coverage: 0.2)
|
|
24
|
+
|
|
25
|
+
# Expand a tile's coverage
|
|
26
|
+
client.expand_tile(tile_id: t1[:tile][:id], amount: 0.1)
|
|
27
|
+
# => { success: true, tile: { coverage: 0.5, ... } }
|
|
28
|
+
|
|
29
|
+
# Explicitly connect two tiles
|
|
30
|
+
client.connect_tiles(tile_a_id: t1[:tile][:id], tile_b_id: t2[:tile][:id])
|
|
31
|
+
# => { success: true, connected: true, tile_a: "uuid...", tile_b: "uuid..." }
|
|
32
|
+
|
|
33
|
+
# Find tiles with poor fit (gaps)
|
|
34
|
+
client.list_gaps
|
|
35
|
+
# => { success: true, count: 0, tiles: [] }
|
|
36
|
+
|
|
37
|
+
# Decay all tiles (periodic maintenance)
|
|
38
|
+
client.shrink_all
|
|
39
|
+
# => { success: true }
|
|
40
|
+
|
|
41
|
+
# Full tessellation report
|
|
42
|
+
client.tessellation_status
|
|
43
|
+
# => { success: true, total_tiles: 3, overall_coverage: 0.35, coverage_label: :sparse, gap_density: 0.0, mosaics: [...], ... }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle install
|
|
50
|
+
bundle exec rspec
|
|
51
|
+
bundle exec rubocop
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_tessellation/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-tessellation'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveTessellation::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
spec.summary = 'Cognitive pattern tiling and coverage analysis for LegionIO agents'
|
|
11
|
+
spec.description = 'Models how cognitive patterns tile together to cover conceptual space ' \
|
|
12
|
+
'detecting gaps, overlaps, and coherence in knowledge mosaics'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-tessellation'
|
|
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/master/README.md",
|
|
21
|
+
'changelog_uri' => "#{spec.homepage}/blob/master/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 CognitiveTessellation
|
|
6
|
+
class Client
|
|
7
|
+
include Runners::CognitiveTessellation
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@default_engine = Helpers::TessellationEngine.new
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTessellation
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_TILES = 500
|
|
9
|
+
MAX_MOSAICS = 50
|
|
10
|
+
|
|
11
|
+
DEFAULT_COVERAGE = 0.0
|
|
12
|
+
FULL_COVERAGE_THRESHOLD = 0.95
|
|
13
|
+
GAP_THRESHOLD = 0.3
|
|
14
|
+
OVERLAP_THRESHOLD = 0.7
|
|
15
|
+
FIT_TOLERANCE = 0.05
|
|
16
|
+
|
|
17
|
+
COVERAGE_GROWTH = 0.08
|
|
18
|
+
COVERAGE_DECAY = 0.02
|
|
19
|
+
OVERLAP_PENALTY = 0.05
|
|
20
|
+
|
|
21
|
+
TILE_SHAPES = %i[
|
|
22
|
+
triangular square hexagonal pentagonal
|
|
23
|
+
irregular fractal amorphous crystalline
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
DOMAINS = %i[
|
|
27
|
+
cognitive emotional procedural semantic
|
|
28
|
+
episodic social creative analytical
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
TILE_TYPES = %i[
|
|
32
|
+
knowledge skill pattern belief
|
|
33
|
+
heuristic schema model framework
|
|
34
|
+
].freeze
|
|
35
|
+
|
|
36
|
+
COVERAGE_LABELS = {
|
|
37
|
+
(0.9..) => :complete,
|
|
38
|
+
(0.7...0.9) => :substantial,
|
|
39
|
+
(0.5...0.7) => :partial,
|
|
40
|
+
(0.3...0.5) => :sparse,
|
|
41
|
+
(..0.3) => :fragmented
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
FIT_LABELS = {
|
|
45
|
+
(0.8..) => :seamless,
|
|
46
|
+
(0.6...0.8) => :snug,
|
|
47
|
+
(0.4...0.6) => :loose,
|
|
48
|
+
(0.2...0.4) => :gapped,
|
|
49
|
+
(..0.2) => :disjointed
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
DENSITY_LABELS = {
|
|
53
|
+
(0.8..) => :saturated,
|
|
54
|
+
(0.6...0.8) => :dense,
|
|
55
|
+
(0.4...0.6) => :moderate,
|
|
56
|
+
(0.2...0.4) => :thin,
|
|
57
|
+
(..0.2) => :vacant
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
def self.label_for(labels, value)
|
|
61
|
+
labels.each { |range, label| return label if range.cover?(value) }
|
|
62
|
+
:unknown
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTessellation
|
|
6
|
+
module Helpers
|
|
7
|
+
class Mosaic
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :id, :domain, :tiles, :created_at
|
|
11
|
+
|
|
12
|
+
def initialize(domain:)
|
|
13
|
+
@id = SecureRandom.uuid
|
|
14
|
+
@domain = domain.to_sym
|
|
15
|
+
@tiles = []
|
|
16
|
+
@created_at = Time.now.utc
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_tile(tile)
|
|
20
|
+
@tiles << tile
|
|
21
|
+
auto_connect(tile)
|
|
22
|
+
tile
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def total_coverage
|
|
26
|
+
return 0.0 if @tiles.empty?
|
|
27
|
+
|
|
28
|
+
raw = @tiles.sum(&:effective_coverage) / @tiles.size
|
|
29
|
+
raw.clamp(0.0, 1.0).round(10)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def average_fit
|
|
33
|
+
return 0.0 if @tiles.empty?
|
|
34
|
+
|
|
35
|
+
(@tiles.sum(&:fit_score) / @tiles.size).round(10)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def gap_count = @tiles.count(&:gapped?)
|
|
39
|
+
def seamless_count = @tiles.count(&:seamless?)
|
|
40
|
+
def isolated_count = @tiles.count(&:isolated?)
|
|
41
|
+
def complete? = total_coverage >= FULL_COVERAGE_THRESHOLD
|
|
42
|
+
|
|
43
|
+
def coherence
|
|
44
|
+
return 0.0 if @tiles.empty?
|
|
45
|
+
|
|
46
|
+
connected = @tiles.count(&:well_connected?)
|
|
47
|
+
(connected.to_f / @tiles.size).round(10)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def uniformity
|
|
51
|
+
return 1.0 if @tiles.size <= 1
|
|
52
|
+
|
|
53
|
+
coverages = @tiles.map(&:coverage)
|
|
54
|
+
mean = coverages.sum / coverages.size
|
|
55
|
+
variance = coverages.sum { |c| (c - mean)**2 } / coverages.size
|
|
56
|
+
(1.0 - Math.sqrt(variance)).clamp(0.0, 1.0).round(10)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_h
|
|
60
|
+
{
|
|
61
|
+
id: @id,
|
|
62
|
+
domain: @domain,
|
|
63
|
+
tile_count: @tiles.size,
|
|
64
|
+
total_coverage: total_coverage,
|
|
65
|
+
average_fit: average_fit,
|
|
66
|
+
coherence: coherence,
|
|
67
|
+
uniformity: uniformity,
|
|
68
|
+
gaps: gap_count,
|
|
69
|
+
seamless: seamless_count,
|
|
70
|
+
complete: complete?
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def auto_connect(new_tile)
|
|
77
|
+
same_domain = @tiles.select { |t| t.domain == new_tile.domain && t.id != new_tile.id }
|
|
78
|
+
same_domain.last(2).each do |neighbor|
|
|
79
|
+
new_tile.connect!(neighbor.id)
|
|
80
|
+
neighbor.connect!(new_tile.id)
|
|
81
|
+
compute_fit(new_tile, neighbor)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def compute_fit(tile_a, tile_b)
|
|
86
|
+
shape_match = tile_a.shape == tile_b.shape ? 0.2 : 0.0
|
|
87
|
+
type_match = tile_a.tile_type == tile_b.tile_type ? 0.1 : 0.0
|
|
88
|
+
base_fit = 0.5 + shape_match + type_match + (rand * 0.2)
|
|
89
|
+
tile_a.adjust_fit!(base_fit)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveTessellation
|
|
6
|
+
module Helpers
|
|
7
|
+
class TessellationEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@tiles = {}
|
|
12
|
+
@mosaics = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_tile(tile_type:, shape:, domain:, coverage: nil, fit_score: nil)
|
|
16
|
+
tile = Tile.new(tile_type: tile_type, shape: shape, domain: domain,
|
|
17
|
+
coverage: coverage, fit_score: fit_score)
|
|
18
|
+
@tiles[tile.id] = tile
|
|
19
|
+
mosaic = find_or_create_mosaic(domain: domain)
|
|
20
|
+
mosaic.add_tile(tile)
|
|
21
|
+
prune_tiles
|
|
22
|
+
tile
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def expand_tile(tile_id:, amount: COVERAGE_GROWTH)
|
|
26
|
+
tile = @tiles[tile_id]
|
|
27
|
+
return nil unless tile
|
|
28
|
+
|
|
29
|
+
tile.expand!(amount)
|
|
30
|
+
tile
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def shrink_all!
|
|
34
|
+
@tiles.each_value { |t| t.shrink!(COVERAGE_DECAY) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def connect_tiles(tile_a_id:, tile_b_id:)
|
|
38
|
+
a = @tiles[tile_a_id]
|
|
39
|
+
b = @tiles[tile_b_id]
|
|
40
|
+
return nil unless a && b
|
|
41
|
+
|
|
42
|
+
a.connect!(b.id)
|
|
43
|
+
b.connect!(a.id)
|
|
44
|
+
{ connected: true, tile_a: a.id, tile_b: b.id }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tiles_by_domain(domain:) = @tiles.values.select { |t| t.domain == domain.to_sym }
|
|
48
|
+
def tiles_by_type(tile_type:) = @tiles.values.select { |t| t.tile_type == tile_type.to_sym }
|
|
49
|
+
def tiles_by_shape(shape:) = @tiles.values.select { |t| t.shape == shape.to_sym }
|
|
50
|
+
def gapped_tiles = @tiles.values.select(&:gapped?)
|
|
51
|
+
def seamless_tiles = @tiles.values.select(&:seamless?)
|
|
52
|
+
def isolated_tiles = @tiles.values.select(&:isolated?)
|
|
53
|
+
def full_coverage_tiles = @tiles.values.select(&:full_coverage?)
|
|
54
|
+
|
|
55
|
+
def overall_coverage
|
|
56
|
+
return 0.0 if @mosaics.empty?
|
|
57
|
+
|
|
58
|
+
(@mosaics.values.sum(&:total_coverage) / @mosaics.size).round(10)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def overall_fit
|
|
62
|
+
return 0.0 if @tiles.empty?
|
|
63
|
+
|
|
64
|
+
(@tiles.values.sum(&:fit_score) / @tiles.size).round(10)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def overall_coherence
|
|
68
|
+
return 0.0 if @mosaics.empty?
|
|
69
|
+
|
|
70
|
+
(@mosaics.values.sum(&:coherence) / @mosaics.size).round(10)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def domain_coverage
|
|
74
|
+
@mosaics.transform_values(&:total_coverage)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def gap_density
|
|
78
|
+
return 0.0 if @tiles.empty?
|
|
79
|
+
|
|
80
|
+
(gapped_tiles.size.to_f / @tiles.size).round(10)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def most_covered(limit: 5)
|
|
84
|
+
@tiles.values.sort_by { |t| -t.effective_coverage }.first(limit)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def least_covered(limit: 5)
|
|
88
|
+
@tiles.values.sort_by(&:effective_coverage).first(limit)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def tessellation_report
|
|
92
|
+
{
|
|
93
|
+
total_tiles: @tiles.size,
|
|
94
|
+
total_mosaics: @mosaics.size,
|
|
95
|
+
overall_coverage: overall_coverage,
|
|
96
|
+
coverage_label: Constants.label_for(COVERAGE_LABELS, overall_coverage),
|
|
97
|
+
overall_fit: overall_fit,
|
|
98
|
+
fit_label: Constants.label_for(FIT_LABELS, overall_fit),
|
|
99
|
+
overall_coherence: overall_coherence,
|
|
100
|
+
gap_density: gap_density,
|
|
101
|
+
gapped_count: gapped_tiles.size,
|
|
102
|
+
seamless_count: seamless_tiles.size,
|
|
103
|
+
isolated_count: isolated_tiles.size,
|
|
104
|
+
domain_coverage: domain_coverage,
|
|
105
|
+
mosaics: @mosaics.values.map(&:to_h)
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def to_h
|
|
110
|
+
{
|
|
111
|
+
total_tiles: @tiles.size,
|
|
112
|
+
total_mosaics: @mosaics.size,
|
|
113
|
+
coverage: overall_coverage,
|
|
114
|
+
fit: overall_fit,
|
|
115
|
+
coherence: overall_coherence
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def find_or_create_mosaic(domain:)
|
|
122
|
+
key = domain.to_sym
|
|
123
|
+
@mosaics[key] ||= Mosaic.new(domain: key)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def prune_tiles
|
|
127
|
+
return if @tiles.size <= MAX_TILES
|
|
128
|
+
|
|
129
|
+
sorted = @tiles.values.sort_by(&:effective_coverage)
|
|
130
|
+
to_remove = sorted.first(@tiles.size - MAX_TILES)
|
|
131
|
+
to_remove.each { |t| @tiles.delete(t.id) }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|