lex-cognitive-zeitgeist 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 +1 -0
- data/.rubocop.yml +44 -0
- data/CLAUDE.md +124 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +78 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/lex-cognitive-zeitgeist.gemspec +33 -0
- data/lib/legion/extensions/cognitive_zeitgeist/helpers/client.rb +13 -0
- data/lib/legion/extensions/cognitive_zeitgeist/helpers/cognitive_signal.rb +34 -0
- data/lib/legion/extensions/cognitive_zeitgeist/helpers/constants.rb +60 -0
- data/lib/legion/extensions/cognitive_zeitgeist/helpers/trend_window.rb +76 -0
- data/lib/legion/extensions/cognitive_zeitgeist/helpers/zeitgeist_engine.rb +153 -0
- data/lib/legion/extensions/cognitive_zeitgeist/runners/cognitive_zeitgeist.rb +95 -0
- data/lib/legion/extensions/cognitive_zeitgeist/version.rb +9 -0
- data/lib/legion/extensions/cognitive_zeitgeist.rb +19 -0
- data/spec/legion/extensions/cognitive_zeitgeist/helpers/cognitive_signal_spec.rb +62 -0
- data/spec/legion/extensions/cognitive_zeitgeist/helpers/constants_spec.rb +73 -0
- data/spec/legion/extensions/cognitive_zeitgeist/helpers/trend_window_spec.rb +100 -0
- data/spec/legion/extensions/cognitive_zeitgeist/helpers/zeitgeist_engine_spec.rb +163 -0
- data/spec/legion/extensions/cognitive_zeitgeist/runners/cognitive_zeitgeist_spec.rb +165 -0
- data/spec/spec_helper.rb +37 -0
- metadata +86 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 226b7e1ceb0957c06b2834e644e4a7b96eaa2cfffdf010f8db4fd9bf78426e5e
|
|
4
|
+
data.tar.gz: e422848c93bf5e2c4b9a0d878503161652640574e852a8b4f4989e03d1ea6118
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1f5188ea227b211c1ada23e059e65795b068b3e6bc7bec7a8eaee2c0b5eca369fdf8bbf5cf5fd3932d4ef6e47a633edf73c411d3235cfef432b964e213708094
|
|
7
|
+
data.tar.gz: 64b0a0699ad11414fb1a07fd1a1397969148a4d6daf677783a25dd3665934dd0a96dad9b095b092f93d9e3e92891b29172226e9fde7f07cd7a1626859f0d206a
|
|
@@ -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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Layout/LineLength:
|
|
7
|
+
Max: 160
|
|
8
|
+
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
|
|
12
|
+
Layout/HashAlignment:
|
|
13
|
+
EnforcedHashRocketStyle: table
|
|
14
|
+
EnforcedColonStyle: table
|
|
15
|
+
|
|
16
|
+
Metrics/ParameterLists:
|
|
17
|
+
Max: 8
|
|
18
|
+
MaxOptionalParameters: 8
|
|
19
|
+
|
|
20
|
+
Metrics/MethodLength:
|
|
21
|
+
Max: 25
|
|
22
|
+
|
|
23
|
+
Metrics/ClassLength:
|
|
24
|
+
Max: 150
|
|
25
|
+
|
|
26
|
+
Metrics/AbcSize:
|
|
27
|
+
Max: 25
|
|
28
|
+
|
|
29
|
+
Metrics/BlockLength:
|
|
30
|
+
Exclude:
|
|
31
|
+
- 'spec/**/*'
|
|
32
|
+
|
|
33
|
+
Style/Documentation:
|
|
34
|
+
Enabled: false
|
|
35
|
+
|
|
36
|
+
Style/OneClassPerFile:
|
|
37
|
+
Exclude:
|
|
38
|
+
- 'spec/spec_helper.rb'
|
|
39
|
+
|
|
40
|
+
Naming/PredicateMethod:
|
|
41
|
+
Enabled: false
|
|
42
|
+
|
|
43
|
+
Naming/PredicatePrefix:
|
|
44
|
+
Enabled: false
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# lex-cognitive-zeitgeist
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-cognitive-zeitgeist`
|
|
6
|
+
- **Version**: 0.1.0
|
|
7
|
+
- **Namespace**: `Legion::Extensions::CognitiveZeitgeist`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Captures the aggregate "spirit of the moment" across cognitive subsystems by ingesting signals from multiple sources and computing collective mood, convergence, and momentum. Named after the German concept of the "spirit of the age" — the pervasive cognitive atmosphere emerging from all active subsystem signals rather than any individual source. Surfaces dominant themes, rising/falling domain trends, and alerts when subsystems diverge.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Gemspec**: `lex-cognitive-zeitgeist.gemspec`
|
|
16
|
+
- **Require**: `lex-cognitive-zeitgeist`
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
- **License**: MIT
|
|
19
|
+
- **Homepage**: https://github.com/LegionIO/lex-cognitive-zeitgeist
|
|
20
|
+
|
|
21
|
+
## File Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/legion/extensions/cognitive_zeitgeist/
|
|
25
|
+
version.rb
|
|
26
|
+
helpers/
|
|
27
|
+
constants.rb # Signal domains, label tables for mood/convergence/momentum
|
|
28
|
+
cognitive_signal.rb # CognitiveSignal class — one subsystem signal
|
|
29
|
+
trend_window.rb # TrendWindow — sliding window over recent signals
|
|
30
|
+
zeitgeist_engine.rb # ZeitgeistEngine — registry, analytics
|
|
31
|
+
client.rb # Client lives at helpers/client.rb (unusual location)
|
|
32
|
+
runners/
|
|
33
|
+
cognitive_zeitgeist.rb # Runner module — public API
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Note: `client.rb` is at `helpers/client.rb`, not the standard `lib/legion/extensions/cognitive_zeitgeist/client.rb`.
|
|
37
|
+
|
|
38
|
+
## Key Constants
|
|
39
|
+
|
|
40
|
+
| Constant | Value | Meaning |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `MAX_SIGNALS` | 1000 | Ring buffer for all ingested signals |
|
|
43
|
+
| `WINDOW_SIZE` | 100 | Sliding window size for `TrendWindow` |
|
|
44
|
+
| `DEFAULT_INTENSITY` | 0.5 | Default signal intensity |
|
|
45
|
+
| `MOMENTUM_THRESHOLD` | 0.3 | Absolute momentum threshold for `accelerating?` / `decelerating?` |
|
|
46
|
+
| `CONVERGENCE_THRESHOLD` | 0.7 | `convergence_alert?` if above this (not used — see `DIVERGENCE_THRESHOLD`) |
|
|
47
|
+
| `DIVERGENCE_THRESHOLD` | 0.3 | `divergence_alert?` if cognitive_convergence < this |
|
|
48
|
+
|
|
49
|
+
`SIGNAL_DOMAINS`: `[:threat, :opportunity, :curiosity, :anxiety, :creativity, :routine, :social, :abstract]`
|
|
50
|
+
|
|
51
|
+
Mood labels (applied to normalized valence, -1..1 mapped to 0..1): `0.8+` = `:euphoric`, `0.6..0.8` = `:elevated`, `0.4..0.6` = `:neutral`, `0.2..0.4` = `:subdued`, `< 0.2` = `:suppressed`
|
|
52
|
+
|
|
53
|
+
Convergence labels: `0.8+` = `:unified`, `0.6..0.8` = `:aligned`, `0.4..0.6` = `:mixed`, `0.2..0.4` = `:fragmented`, `< 0.2` = `:divergent`
|
|
54
|
+
|
|
55
|
+
Momentum labels (can be negative): `0.6+` = `:surging`, `0.3..0.6` = `:building`, `0.0..0.3` = `:steady`, `-0.3..0.0` = `:fading`, `< -0.3` = `:collapsing`
|
|
56
|
+
|
|
57
|
+
`Constants.label_for(labels_hash, value)` — iterates hash by range cover; returns nil if no match.
|
|
58
|
+
|
|
59
|
+
## Key Classes
|
|
60
|
+
|
|
61
|
+
### `Helpers::CognitiveSignal`
|
|
62
|
+
|
|
63
|
+
One signal from a cognitive subsystem.
|
|
64
|
+
|
|
65
|
+
- Fields: `id` (UUID), `source_subsystem` (symbol), `domain` (symbol), `intensity` (0.0–1.0), `valence` (-1.0–1.0), `timestamp`
|
|
66
|
+
- `valence` is bidirectional (-1.0 negative through +1.0 positive) — unlike most other extensions which use unsigned intensity
|
|
67
|
+
|
|
68
|
+
### `Helpers::TrendWindow`
|
|
69
|
+
|
|
70
|
+
Sliding window (default 100 signals) for local trend analysis.
|
|
71
|
+
|
|
72
|
+
- `add(signal)` — appends and shifts oldest when over `window_size`; returns `self`
|
|
73
|
+
- `dominant_domain` — domain with highest total intensity weight across window signals
|
|
74
|
+
- `dominant_valence` — intensity-weighted mean valence across window
|
|
75
|
+
- `momentum` — delta between second-half avg intensity and first-half avg intensity; positive = accelerating
|
|
76
|
+
- `accelerating?` — momentum > 0.3; `decelerating?` — momentum < -0.3
|
|
77
|
+
|
|
78
|
+
### `Helpers::ZeitgeistEngine`
|
|
79
|
+
|
|
80
|
+
Central registry and analytics engine.
|
|
81
|
+
|
|
82
|
+
- `ingest(source_subsystem:, domain:, intensity:, valence:, timestamp:)` — creates `CognitiveSignal`, appends to `@signals` (ring buffer), adds to `@trend_window`
|
|
83
|
+
- `dominant_themes(limit:)` — domains ranked by total intensity weight; returns `[{ domain:, weight: }]`
|
|
84
|
+
- `collective_mood` — intensity-weighted mean valence across all signals; returns -1.0..1.0
|
|
85
|
+
- `cognitive_convergence` — ratio of subsystems whose dominant domain matches the overall dominant domain; returns 1.0 if <= 1 subsystem; returns 0.5 if no overall dominant found
|
|
86
|
+
- `rising_domains(window_size:)` / `falling_domains(window_size:)` — compare recent half to earlier half of signals; return `[{ domain:, delta: }]` sorted by magnitude
|
|
87
|
+
- `divergence_alert?` — `cognitive_convergence < DIVERGENCE_THRESHOLD` (0.3)
|
|
88
|
+
- `zeitgeist_report` — full report: signal_count, dominant_themes, collective_mood, mood_label, convergence, convergence_label, momentum, momentum_label, rising_domains, falling_domains, divergence_alert, trend_window
|
|
89
|
+
|
|
90
|
+
Mood normalization: `normalize_mood(mood_value) = (mood + 1.0) / 2.0` — maps -1..1 to 0..1 before label lookup.
|
|
91
|
+
|
|
92
|
+
## Runners
|
|
93
|
+
|
|
94
|
+
Module: `Legion::Extensions::CognitiveZeitgeist::Runners::CognitiveZeitgeist`
|
|
95
|
+
|
|
96
|
+
| Runner | Key Args | Returns |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `ingest_signal` | `source_subsystem:`, `domain:`, `intensity:`, `valence:` | `{ success:, signal: }` |
|
|
99
|
+
| `zeitgeist_report` | — | full report merged with `success: true` |
|
|
100
|
+
| `collective_mood` | — | `{ success:, mood:, mood_label: }` |
|
|
101
|
+
| `cognitive_convergence` | — | `{ success:, convergence:, convergence_label:, divergence_alert: }` |
|
|
102
|
+
| `dominant_themes` | `limit:` | `{ success:, themes:, count: }` |
|
|
103
|
+
| `rising_domains` | — | `{ success:, domains:, count: }` |
|
|
104
|
+
| `falling_domains` | — | `{ success:, domains:, count: }` |
|
|
105
|
+
| `trend_window_status` | — | `{ success:, trend_window:, momentum_label: }` |
|
|
106
|
+
|
|
107
|
+
All runners accept optional `engine:` keyword for test injection.
|
|
108
|
+
|
|
109
|
+
## Integration Points
|
|
110
|
+
|
|
111
|
+
- `ingest_signal` should be called by `lex-tick` phase handlers after each phase completes, passing the originating subsystem and domain
|
|
112
|
+
- `divergence_alert?` can trigger conflict escalation in `lex-conflict` when subsystems are pulling in incompatible directions
|
|
113
|
+
- `collective_mood` provides a cross-subsystem mood aggregate that `lex-emotion` can use to modulate valence
|
|
114
|
+
- `dominant_themes` reveals what domains are consuming the most cognitive bandwidth
|
|
115
|
+
- All state is in-memory per `ZeitgeistEngine` instance
|
|
116
|
+
|
|
117
|
+
## Development Notes
|
|
118
|
+
|
|
119
|
+
- `client.rb` is at `helpers/client.rb` — an unusual location for this gem; not at the standard top-level path
|
|
120
|
+
- Valence is signed (-1.0 to 1.0) in this extension; mood label lookup requires normalizing to 0..1 via `(mood + 1.0) / 2.0`
|
|
121
|
+
- `cognitive_convergence` returns 1.0 when only one subsystem has ingested signals (no divergence possible) and 0.5 when no overall dominant domain can be determined
|
|
122
|
+
- `rising_domains` requires `signals.size >= window_size` (default 50) — returns empty array if insufficient history
|
|
123
|
+
- Momentum labels cover negative values (`:fading`, `:collapsing`) — unique among the label tables in this extension category
|
|
124
|
+
- `CONVERGENCE_THRESHOLD` (0.7) is defined but not used; only `DIVERGENCE_THRESHOLD` (0.3) drives alert logic
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
lex-cognitive-zeitgeist (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-zeitgeist!
|
|
73
|
+
rspec (~> 3.13)
|
|
74
|
+
rubocop (~> 1.75)
|
|
75
|
+
rubocop-rspec
|
|
76
|
+
|
|
77
|
+
BUNDLED WITH
|
|
78
|
+
2.6.9
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Esity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# lex-cognitive-zeitgeist
|
|
2
|
+
|
|
3
|
+
A LegionIO cognitive architecture extension that captures the aggregate cognitive atmosphere across subsystems. Named after the German "spirit of the age" — surfaces dominant themes, collective mood, convergence, and momentum from the stream of signals flowing through the cognitive system.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Ingests **cognitive signals** from multiple subsystems and computes cross-cutting metrics:
|
|
8
|
+
|
|
9
|
+
- **Collective mood**: intensity-weighted mean valence across all signals (-1.0 to +1.0)
|
|
10
|
+
- **Cognitive convergence**: ratio of subsystems focused on the same dominant domain; low convergence triggers a divergence alert
|
|
11
|
+
- **Momentum**: whether signal intensity is accelerating or fading in the recent window
|
|
12
|
+
- **Dominant themes**: domains with highest total intensity weight
|
|
13
|
+
- **Rising/falling domains**: comparing recent activity to historical baseline
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'lex-cognitive-zeitgeist'
|
|
19
|
+
|
|
20
|
+
client = Legion::Extensions::CognitiveZeitgeist::Client.new
|
|
21
|
+
|
|
22
|
+
# Ingest signals from different subsystems
|
|
23
|
+
client.ingest_signal(source_subsystem: :emotion, domain: :anxiety, intensity: 0.7, valence: -0.6)
|
|
24
|
+
client.ingest_signal(source_subsystem: :prediction, domain: :threat, intensity: 0.8, valence: -0.4)
|
|
25
|
+
client.ingest_signal(source_subsystem: :memory, domain: :curiosity, intensity: 0.5, valence: 0.3)
|
|
26
|
+
|
|
27
|
+
# Get collective mood
|
|
28
|
+
client.collective_mood
|
|
29
|
+
# => { success: true, mood: -0.4, mood_label: :subdued }
|
|
30
|
+
|
|
31
|
+
# Check whether subsystems are aligned
|
|
32
|
+
client.cognitive_convergence
|
|
33
|
+
# => { success: true, convergence: 0.67, convergence_label: :aligned, divergence_alert: false }
|
|
34
|
+
|
|
35
|
+
# Dominant themes (by intensity weight)
|
|
36
|
+
client.dominant_themes(limit: 3)
|
|
37
|
+
# => { success: true, themes: [{ domain: :threat, weight: 0.8 }, ...], count: 3 }
|
|
38
|
+
|
|
39
|
+
# Rising and falling domains
|
|
40
|
+
client.rising_domains
|
|
41
|
+
# => { success: true, domains: [{ domain: :threat, delta: 0.3 }], count: 1 }
|
|
42
|
+
|
|
43
|
+
# Trend window status
|
|
44
|
+
client.trend_window_status
|
|
45
|
+
# => { success: true, trend_window: { size: 3, dominant_domain: :threat, momentum: 0.1, ... }, momentum_label: :steady }
|
|
46
|
+
|
|
47
|
+
# Full zeitgeist report
|
|
48
|
+
client.zeitgeist_report
|
|
49
|
+
# => { success: true, signal_count: 3, dominant_themes: [...], collective_mood: -0.4,
|
|
50
|
+
# mood_label: :subdued, convergence: 0.67, convergence_label: :aligned,
|
|
51
|
+
# momentum: 0.1, momentum_label: :steady, divergence_alert: false, ... }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle install
|
|
58
|
+
bundle exec rspec
|
|
59
|
+
bundle exec rubocop
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_zeitgeist/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-zeitgeist'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveZeitgeist::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Collective cognitive zeitgeist detection for LegionIO agents'
|
|
12
|
+
spec.description = 'Detects the overall mood and trending concerns across all cognitive subsystems. ' \
|
|
13
|
+
'Captures what the agent mind is collectively focused on via signal ingestion, ' \
|
|
14
|
+
'trend windows, convergence scoring, and divergence alerts.'
|
|
15
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-zeitgeist'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
18
|
+
spec.required_ruby_version = '>= 3.4'
|
|
19
|
+
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-zeitgeist'
|
|
22
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-zeitgeist'
|
|
23
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-zeitgeist/blob/master/CHANGELOG.md'
|
|
24
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-zeitgeist/issues'
|
|
25
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
26
|
+
|
|
27
|
+
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
|
|
28
|
+
ls.readlines("\x0", chomp: true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
spec.require_paths = ['lib']
|
|
32
|
+
spec.add_development_dependency 'legion-gaia'
|
|
33
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveZeitgeist
|
|
6
|
+
module Helpers
|
|
7
|
+
class CognitiveSignal
|
|
8
|
+
attr_reader :id, :source_subsystem, :domain, :intensity, :valence, :timestamp
|
|
9
|
+
|
|
10
|
+
def initialize(source_subsystem:, domain:, intensity: Constants::DEFAULT_INTENSITY,
|
|
11
|
+
valence: 0.0, timestamp: nil)
|
|
12
|
+
@id = SecureRandom.uuid
|
|
13
|
+
@source_subsystem = source_subsystem.to_sym
|
|
14
|
+
@domain = domain.to_sym
|
|
15
|
+
@intensity = intensity.to_f.clamp(0.0, 1.0)
|
|
16
|
+
@valence = valence.to_f.clamp(-1.0, 1.0)
|
|
17
|
+
@timestamp = timestamp || Time.now.utc
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
{
|
|
22
|
+
id: @id,
|
|
23
|
+
source_subsystem: @source_subsystem,
|
|
24
|
+
domain: @domain,
|
|
25
|
+
intensity: @intensity.round(10),
|
|
26
|
+
valence: @valence.round(10),
|
|
27
|
+
timestamp: @timestamp.iso8601
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveZeitgeist
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_SIGNALS = 1000
|
|
9
|
+
WINDOW_SIZE = 100
|
|
10
|
+
DEFAULT_INTENSITY = 0.5
|
|
11
|
+
MOMENTUM_THRESHOLD = 0.3
|
|
12
|
+
CONVERGENCE_THRESHOLD = 0.7
|
|
13
|
+
DIVERGENCE_THRESHOLD = 0.3
|
|
14
|
+
|
|
15
|
+
SIGNAL_DOMAINS = %i[
|
|
16
|
+
threat
|
|
17
|
+
opportunity
|
|
18
|
+
curiosity
|
|
19
|
+
anxiety
|
|
20
|
+
creativity
|
|
21
|
+
routine
|
|
22
|
+
social
|
|
23
|
+
abstract
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
MOOD_LABELS = {
|
|
27
|
+
(0.8..) => :euphoric,
|
|
28
|
+
(0.6...0.8) => :elevated,
|
|
29
|
+
(0.4...0.6) => :neutral,
|
|
30
|
+
(0.2...0.4) => :subdued,
|
|
31
|
+
(..0.2) => :suppressed
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
CONVERGENCE_LABELS = {
|
|
35
|
+
(0.8..) => :unified,
|
|
36
|
+
(0.6...0.8) => :aligned,
|
|
37
|
+
(0.4...0.6) => :mixed,
|
|
38
|
+
(0.2...0.4) => :fragmented,
|
|
39
|
+
(..0.2) => :divergent
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
MOMENTUM_LABELS = {
|
|
43
|
+
(0.6..) => :surging,
|
|
44
|
+
(0.3...0.6) => :building,
|
|
45
|
+
(0.0...0.3) => :steady,
|
|
46
|
+
(-0.3...0.0) => :fading,
|
|
47
|
+
(..-0.3) => :collapsing
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
def self.label_for(labels_hash, value)
|
|
51
|
+
labels_hash.each do |range, label|
|
|
52
|
+
return label if range.cover?(value)
|
|
53
|
+
end
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveZeitgeist
|
|
6
|
+
module Helpers
|
|
7
|
+
class TrendWindow
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :signals, :window_size
|
|
11
|
+
|
|
12
|
+
def initialize(window_size: WINDOW_SIZE)
|
|
13
|
+
@signals = []
|
|
14
|
+
@window_size = window_size
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add(signal)
|
|
18
|
+
@signals << signal
|
|
19
|
+
@signals.shift while @signals.size > @window_size
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def dominant_domain
|
|
24
|
+
return nil if @signals.empty?
|
|
25
|
+
|
|
26
|
+
counts = Hash.new(0.0)
|
|
27
|
+
@signals.each { |s| counts[s.domain] += s.intensity }
|
|
28
|
+
counts.max_by { |_d, weight| weight }&.first
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def dominant_valence
|
|
32
|
+
return 0.0 if @signals.empty?
|
|
33
|
+
|
|
34
|
+
weighted_sum = @signals.sum { |s| s.valence * s.intensity }
|
|
35
|
+
total_intensity = @signals.sum(&:intensity)
|
|
36
|
+
return 0.0 if total_intensity.zero?
|
|
37
|
+
|
|
38
|
+
(weighted_sum / total_intensity).clamp(-1.0, 1.0).round(10)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def momentum
|
|
42
|
+
return 0.0 if @signals.size < 2
|
|
43
|
+
|
|
44
|
+
half = @signals.size / 2
|
|
45
|
+
first_half = @signals.first(half)
|
|
46
|
+
second_half = @signals.last(half)
|
|
47
|
+
|
|
48
|
+
avg_intensity = ->(arr) { arr.sum(&:intensity) / arr.size.to_f }
|
|
49
|
+
delta = avg_intensity.call(second_half) - avg_intensity.call(first_half)
|
|
50
|
+
delta.clamp(-1.0, 1.0).round(10)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def accelerating?
|
|
54
|
+
momentum > MOMENTUM_THRESHOLD
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def decelerating?
|
|
58
|
+
momentum < -MOMENTUM_THRESHOLD
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_h
|
|
62
|
+
{
|
|
63
|
+
size: @signals.size,
|
|
64
|
+
window_size: @window_size,
|
|
65
|
+
dominant_domain: dominant_domain,
|
|
66
|
+
dominant_valence: dominant_valence,
|
|
67
|
+
momentum: momentum,
|
|
68
|
+
accelerating: accelerating?,
|
|
69
|
+
decelerating: decelerating?
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|