lex-cognitive-blindspot 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: 70f03cdc2eef30b1868b9f42870fb55b50ad4ebbdfebfcfe21c745a00ef7a838
4
+ data.tar.gz: 07bf45977e84481f64ce514a200eeb2f6b10c7495079204df988865dc61da9db
5
+ SHA512:
6
+ metadata.gz: 31569326fc31e1fb955be407e857f748e3f52824451b584f59052c15f3748a34948139d2c91653c32d6a064ea6195806eb8d60751a79dea9d6acd83f82abc18e
7
+ data.tar.gz: 70ecbaac03f808079e9ca8385a2bfe62538f869b94eba230e09d096150a19f745b7c3de92951be703771c09d7f83155d818a162811dee5047c44ced83bcd47d7
@@ -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
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,40 @@
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
+ EnforcedHashRocketStyle: table
29
+ EnforcedColonStyle: table
30
+
31
+ Metrics/BlockLength:
32
+ Exclude:
33
+ - 'spec/**/*'
34
+
35
+ Style/FrozenStringLiteralComment:
36
+ Enabled: true
37
+
38
+ Style/OneClassPerFile:
39
+ Exclude:
40
+ - 'spec/spec_helper.rb'
data/CLAUDE.md ADDED
@@ -0,0 +1,81 @@
1
+ # lex-cognitive-blindspot
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
+ Johari Window for AI. Tracks unknown-unknowns, manages knowledge boundaries, and scores agent awareness with blindspot acknowledgement and mitigation strategies. Models the progression from unknown-unknown (active blindspot) to known-unknown (acknowledged) to mitigated to resolved.
10
+
11
+ ## Gem Info
12
+
13
+ - **Gem name**: `lex-cognitive-blindspot`
14
+ - **Version**: `0.1.0`
15
+ - **Module**: `Legion::Extensions::CognitiveBlindspot`
16
+ - **Ruby**: `>= 3.4`
17
+ - **License**: MIT
18
+
19
+ ## File Structure
20
+
21
+ ```
22
+ lib/legion/extensions/cognitive_blindspot/
23
+ cognitive_blindspot.rb
24
+ version.rb
25
+ client.rb
26
+ helpers/
27
+ constants.rb
28
+ blindspot.rb
29
+ knowledge_boundary.rb
30
+ blindspot_engine.rb
31
+ runners/
32
+ cognitive_blindspot.rb
33
+ ```
34
+
35
+ ## Key Constants
36
+
37
+ From `helpers/constants.rb`:
38
+
39
+ - `DISCOVERY_METHODS` — `%i[error_analysis peer_feedback cross_domain_check contradiction_detection confidence_calibration external_audit self_reflection unknown]`
40
+ - `MAX_BLINDSPOTS` = `300`, `MAX_BOUNDARIES` = `50`
41
+ - `DEFAULT_SEVERITY` = `0.5`, `SEVERITY_BOOST` = `0.1`
42
+ - `AWARENESS_THRESHOLD` = `0.6`
43
+ - `SEVERITY_LABELS` — `0.8+` = `:critical`, `0.6` = `:high`, `0.4` = `:moderate`, `0.2` = `:low`, below = `:negligible`
44
+ - `AWARENESS_LABELS` — `0.8+` = `:highly_aware` through below `0.2` = `:unaware`
45
+ - `COVERAGE_LABELS` — `0.8+` = `:comprehensive` through below `0.2` = `:minimal`
46
+ - `STATUS_LABELS` — describes each state: `active` = unknown-unknown, `acknowledged` = known-unknown, `mitigated` = partial coverage, `resolved` = fully addressed
47
+
48
+ ## Runners
49
+
50
+ All methods in `Runners::CognitiveBlindspot`:
51
+
52
+ - `register_blindspot(domain:, discovered_by:, description:, severity: DEFAULT_SEVERITY)` — records a new blindspot in `active` state
53
+ - `acknowledge_blindspot(blindspot_id:)` — transitions from `active` to `acknowledged` (unknown-unknown -> known-unknown)
54
+ - `mitigate_blindspot(blindspot_id:, boost: SEVERITY_BOOST)` — applies mitigation; reduces severity by boost amount
55
+ - `resolve_blindspot(blindspot_id:)` — marks as fully resolved
56
+ - `set_knowledge_boundary(domain:, confidence:, coverage_estimate:)` — establishes a domain-level knowledge boundary
57
+ - `detect_boundary_gap(domain:, error_occurred: false)` — checks if errors in a domain indicate boundary gaps
58
+ - `active_blindspots_report` — all unacknowledged or unresolved blindspots
59
+ - `most_severe_report(limit: 5)` — top blindspots by severity
60
+ - `mitigation_strategies_report(domain: nil)` — recommended strategies per domain
61
+ - `coverage_report` — all knowledge boundaries with coverage estimates
62
+ - `johari_report` — full Johari Window breakdown: known-knowns, known-unknowns, unknown-unknowns counts
63
+ - `awareness_score_report` — scalar awareness score, label, and awareness gap
64
+
65
+ ## Helpers
66
+
67
+ - `BlindspotEngine` — stores blindspots and knowledge boundaries; computes awareness score as ratio of acknowledged/mitigated/resolved to total.
68
+ - `Blindspot` — individual blindspot with `domain`, `discovered_by`, `description`, `severity`, `status`. State machine: `active` -> `acknowledged` -> `mitigated` -> `resolved`.
69
+ - `KnowledgeBoundary` — domain-level confidence and coverage estimate; gap detection on error events.
70
+
71
+ ## Integration Points
72
+
73
+ - `lex-cognitive-debugging` detects reasoning errors; those errors are natural inputs to `register_blindspot` (via `discovered_by: :error_analysis`).
74
+ - `lex-tick` can call awareness score checks in the `identity_entropy_check` phase to flag low-awareness domains.
75
+ - `detect_boundary_gap` is meant to be called when errors occur — the caller passes `error_occurred: true` to trigger gap analysis for the relevant domain.
76
+
77
+ ## Development Notes
78
+
79
+ - Blindspot state progression is one-directional: `active` -> `acknowledged` -> `mitigated` -> `resolved`. No regression.
80
+ - `awareness_score` is computed as `(acknowledged + mitigated + resolved) / total_blindspots`. An agent with no registered blindspots returns `0.0` (unaware, not aware).
81
+ - `johari_report` maps: `active` = unknown-unknown quadrant; `acknowledged/mitigated` = known-unknown quadrant; no explicit known-known tracking (that would require a separate knowledge model).
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-blindspot (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-blindspot!
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,40 @@
1
+ # lex-cognitive-blindspot
2
+
3
+ Cognitive blindspot detection for LegionIO agents. Johari Window for AI — tracks unknown-unknowns, manages knowledge boundaries, and scores agent awareness with blindspot acknowledgement and mitigation strategies.
4
+
5
+ ## What It Does
6
+
7
+ Models the progression from unknown-unknown (active blindspot: the agent doesn't know what it doesn't know) to known-unknown (acknowledged: now visible but not addressed) to mitigated (partial coverage) to resolved. Discovery methods include error analysis, peer feedback, contradiction detection, confidence calibration, and self-reflection.
8
+
9
+ A parallel knowledge boundary system tracks domain-level confidence and coverage estimates, with gap detection triggered by error events in a domain.
10
+
11
+ ## Usage
12
+
13
+ ```ruby
14
+ client = Legion::Extensions::CognitiveBlindspot::Client.new
15
+
16
+ spot = client.register_blindspot(
17
+ domain: :temporal_reasoning,
18
+ discovered_by: :error_analysis,
19
+ description: 'Consistently misjudges elapsed time in multi-step plans',
20
+ severity: 0.7
21
+ )
22
+
23
+ client.acknowledge_blindspot(blindspot_id: spot[:id])
24
+ client.mitigate_blindspot(blindspot_id: spot[:id], boost: 0.15)
25
+
26
+ client.johari_report
27
+ client.awareness_score_report
28
+ ```
29
+
30
+ ## Development
31
+
32
+ ```bash
33
+ bundle install
34
+ bundle exec rspec
35
+ bundle exec rubocop
36
+ ```
37
+
38
+ ## License
39
+
40
+ MIT
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_blindspot/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-blindspot'
7
+ spec.version = Legion::Extensions::CognitiveBlindspot::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Cognitive blindspot detection for LegionIO agents'
12
+ spec.description = 'Johari Window for AI — tracks unknown-unknowns, ' \
13
+ 'manages knowledge boundaries, and scores agent awareness ' \
14
+ 'with blindspot acknowledgement and mitigation strategies.'
15
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-blindspot'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.4'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = spec.homepage
21
+ spec.metadata['documentation_uri'] = "#{spec.homepage}/blob/main/README.md"
22
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
+ spec.metadata['bug_tracker_uri'] = "#{spec.homepage}/issues"
24
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
+
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{\A(?:test|spec|features)/})
29
+ end
30
+ end
31
+ spec.require_paths = ['lib']
32
+ spec.add_development_dependency 'legion-gaia'
33
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveBlindspot
6
+ class Client
7
+ include Runners::CognitiveBlindspot
8
+
9
+ def engine
10
+ @engine ||= Helpers::BlindspotEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveBlindspot
8
+ module Helpers
9
+ class Blindspot
10
+ include Constants
11
+
12
+ attr_reader :id, :domain, :discovered_by, :severity, :description,
13
+ :status, :created_at, :acknowledged_at, :mitigated_at, :resolved_at
14
+
15
+ def initialize(domain:, discovered_by:, description:, severity: DEFAULT_SEVERITY)
16
+ @id = SecureRandom.uuid
17
+ @domain = domain.to_sym
18
+ @discovered_by = discovered_by.to_sym
19
+ @description = description.to_s
20
+ @severity = severity.to_f.clamp(0.0, 1.0)
21
+ @status = :active
22
+ @created_at = Time.now.utc
23
+ @acknowledged_at = nil
24
+ @mitigated_at = nil
25
+ @resolved_at = nil
26
+ end
27
+
28
+ def severity_label
29
+ Constants.label_for(SEVERITY_LABELS, @severity) || :negligible
30
+ end
31
+
32
+ def active?
33
+ @status == :active
34
+ end
35
+
36
+ def acknowledged?
37
+ @status == :acknowledged || @status == :mitigated || @status == :resolved
38
+ end
39
+
40
+ def resolved?
41
+ @status == :resolved
42
+ end
43
+
44
+ def acknowledge!
45
+ return self if @status != :active
46
+
47
+ @status = :acknowledged
48
+ @acknowledged_at = Time.now.utc
49
+ self
50
+ end
51
+
52
+ def mitigate!(boost: SEVERITY_BOOST)
53
+ acknowledge! if @status == :active
54
+ @status = :mitigated
55
+ @mitigated_at = Time.now.utc
56
+ @severity = (@severity - boost).clamp(0.0, 1.0).round(10)
57
+ self
58
+ end
59
+
60
+ def resolve!
61
+ acknowledge! if @status == :active
62
+ @status = :resolved
63
+ @resolved_at = Time.now.utc
64
+ self
65
+ end
66
+
67
+ def boost_severity!(amount: SEVERITY_BOOST)
68
+ @severity = (@severity + amount).clamp(0.0, 1.0).round(10)
69
+ self
70
+ end
71
+
72
+ def to_h
73
+ {
74
+ id: @id,
75
+ domain: @domain,
76
+ discovered_by: @discovered_by,
77
+ description: @description,
78
+ severity: @severity,
79
+ severity_label: severity_label,
80
+ status: @status,
81
+ active: active?,
82
+ acknowledged: acknowledged?,
83
+ resolved: resolved?,
84
+ created_at: @created_at,
85
+ acknowledged_at: @acknowledged_at,
86
+ mitigated_at: @mitigated_at,
87
+ resolved_at: @resolved_at
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveBlindspot
6
+ module Helpers
7
+ class BlindspotEngine
8
+ include Constants
9
+
10
+ attr_reader :awareness_score
11
+
12
+ def initialize
13
+ @blindspots = {}
14
+ @boundaries = {}
15
+ @awareness_score = 1.0
16
+ end
17
+
18
+ def register_blindspot(domain:, discovered_by:, description:, severity: DEFAULT_SEVERITY)
19
+ prune_blindspots_if_needed
20
+ blindspot = Blindspot.new(domain: domain, discovered_by: discovered_by,
21
+ description: description, severity: severity)
22
+ @blindspots[blindspot.id] = blindspot
23
+ recalculate_awareness
24
+ blindspot
25
+ end
26
+
27
+ def acknowledge_blindspot(blindspot_id:)
28
+ blindspot = @blindspots.fetch(blindspot_id, nil)
29
+ return { found: false, blindspot_id: blindspot_id } unless blindspot
30
+
31
+ blindspot.acknowledge!
32
+ recalculate_awareness
33
+ { found: true, blindspot_id: blindspot_id, status: blindspot.status,
34
+ awareness_score: @awareness_score.round(10) }
35
+ end
36
+
37
+ def mitigate_blindspot(blindspot_id:, boost: SEVERITY_BOOST)
38
+ blindspot = @blindspots.fetch(blindspot_id, nil)
39
+ return { found: false, blindspot_id: blindspot_id } unless blindspot
40
+
41
+ blindspot.mitigate!(boost: boost)
42
+ recalculate_awareness
43
+ { found: true, blindspot_id: blindspot_id, status: blindspot.status,
44
+ severity: blindspot.severity, awareness_score: @awareness_score.round(10) }
45
+ end
46
+
47
+ def resolve_blindspot(blindspot_id:)
48
+ blindspot = @blindspots.fetch(blindspot_id, nil)
49
+ return { found: false, blindspot_id: blindspot_id } unless blindspot
50
+
51
+ blindspot.resolve!
52
+ recalculate_awareness
53
+ { found: true, blindspot_id: blindspot_id, status: blindspot.status,
54
+ awareness_score: @awareness_score.round(10) }
55
+ end
56
+
57
+ def set_boundary(domain:, confidence: 0.5, coverage_estimate: 0.5)
58
+ prune_boundaries_if_needed
59
+ existing = boundary_for_domain(domain)
60
+ return update_boundary!(existing, confidence, coverage_estimate) if existing
61
+
62
+ boundary = KnowledgeBoundary.new(domain: domain, confidence: confidence,
63
+ coverage_estimate: coverage_estimate)
64
+ @boundaries[boundary.id] = boundary
65
+ boundary
66
+ end
67
+
68
+ def detect_boundary_gap(domain:, error_occurred: false)
69
+ boundary = boundary_for_domain(domain)
70
+ return { gap_detected: false, domain: domain, reason: :no_boundary } unless boundary
71
+
72
+ { gap_detected: boundary.gap_detected?(error_occurred: error_occurred),
73
+ domain: domain, confidence: boundary.confidence,
74
+ coverage: boundary.coverage_estimate }
75
+ end
76
+
77
+ def blindspots_by_domain(domain)
78
+ d = domain.to_sym
79
+ @blindspots.values.select { |b| b.domain == d }
80
+ end
81
+
82
+ def active_blindspots = @blindspots.values.select(&:active?)
83
+ def acknowledged_blindspots = @blindspots.values.select { |b| b.status == :acknowledged }
84
+ def resolved_blindspots = @blindspots.values.select(&:resolved?)
85
+
86
+ def most_severe(limit: 5)
87
+ @blindspots.values.sort_by { |b| -b.severity }.first(limit)
88
+ end
89
+
90
+ def mitigation_strategies(domain: nil)
91
+ spots = domain ? blindspots_by_domain(domain) : @blindspots.values
92
+ spots.select(&:active?).map do |b|
93
+ { blindspot_id: b.id, domain: b.domain, severity: b.severity, strategy: strategy_for(b) }
94
+ end
95
+ end
96
+
97
+ def coverage_report = @boundaries.values.map(&:to_h)
98
+ def awareness_label = Constants.label_for(AWARENESS_LABELS, @awareness_score) || :unaware
99
+ def awareness_gap = (1.0 - @awareness_score).clamp(0.0, 1.0).round(10)
100
+
101
+ def johari_report
102
+ { total_blindspots: @blindspots.size,
103
+ active: active_blindspots.size,
104
+ acknowledged: acknowledged_blindspots.size,
105
+ resolved: resolved_blindspots.size,
106
+ awareness_score: @awareness_score.round(10),
107
+ awareness_label: awareness_label,
108
+ awareness_gap: awareness_gap,
109
+ boundaries_tracked: @boundaries.size,
110
+ most_severe: most_severe(limit: 3).map(&:to_h) }
111
+ end
112
+
113
+ def to_h
114
+ { total_blindspots: @blindspots.size,
115
+ active: active_blindspots.size,
116
+ acknowledged: acknowledged_blindspots.size,
117
+ resolved: resolved_blindspots.size,
118
+ awareness_score: @awareness_score.round(10),
119
+ awareness_label: awareness_label }
120
+ end
121
+
122
+ private
123
+
124
+ def update_boundary!(boundary, confidence, coverage_estimate)
125
+ boundary.update_confidence!(confidence)
126
+ boundary.update_coverage!(coverage_estimate)
127
+ boundary
128
+ end
129
+
130
+ def boundary_for_domain(domain)
131
+ d = domain.to_sym
132
+ @boundaries.values.find { |b| b.domain == d }
133
+ end
134
+
135
+ def recalculate_awareness
136
+ total = @blindspots.size.to_f
137
+ return @awareness_score = 1.0 if total.zero?
138
+
139
+ not_active = @blindspots.values.count { |b| !b.active? }.to_f
140
+ @awareness_score = (not_active / total).clamp(0.0, 1.0).round(10)
141
+ end
142
+
143
+ def strategy_for(blindspot)
144
+ case blindspot.severity
145
+ when (0.8..) then :immediate_external_audit
146
+ when (0.6...0.8) then :cross_domain_check
147
+ when (0.4...0.6) then :peer_feedback
148
+ else :self_reflection
149
+ end
150
+ end
151
+
152
+ def prune_blindspots_if_needed
153
+ return if @blindspots.size < MAX_BLINDSPOTS
154
+
155
+ target = resolved_blindspots.min_by(&:created_at) ||
156
+ @blindspots.values.min_by(&:severity)
157
+ @blindspots.delete(target.id) if target
158
+ end
159
+
160
+ def prune_boundaries_if_needed
161
+ return if @boundaries.size < MAX_BOUNDARIES
162
+
163
+ oldest = @boundaries.values.min_by(&:created_at)
164
+ @boundaries.delete(oldest.id) if oldest
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveBlindspot
6
+ module Helpers
7
+ module Constants
8
+ MAX_BLINDSPOTS = 300
9
+ MAX_BOUNDARIES = 50
10
+ DEFAULT_SEVERITY = 0.5
11
+ SEVERITY_BOOST = 0.1
12
+ AWARENESS_THRESHOLD = 0.6
13
+
14
+ DISCOVERY_METHODS = %i[
15
+ error_analysis peer_feedback cross_domain_check
16
+ contradiction_detection confidence_calibration
17
+ external_audit self_reflection unknown
18
+ ].freeze
19
+
20
+ SEVERITY_LABELS = {
21
+ (0.8..) => :critical,
22
+ (0.6...0.8) => :high,
23
+ (0.4...0.6) => :moderate,
24
+ (0.2...0.4) => :low,
25
+ (..0.2) => :negligible
26
+ }.freeze
27
+
28
+ AWARENESS_LABELS = {
29
+ (0.8..) => :highly_aware,
30
+ (0.6...0.8) => :aware,
31
+ (0.4...0.6) => :partially_blind,
32
+ (0.2...0.4) => :mostly_blind,
33
+ (..0.2) => :unaware
34
+ }.freeze
35
+
36
+ COVERAGE_LABELS = {
37
+ (0.8..) => :comprehensive,
38
+ (0.6...0.8) => :substantial,
39
+ (0.4...0.6) => :partial,
40
+ (0.2...0.4) => :limited,
41
+ (..0.2) => :minimal
42
+ }.freeze
43
+
44
+ STATUS_LABELS = {
45
+ active: 'Unknown unknown — not yet surfaced to awareness',
46
+ acknowledged: 'Known unknown — surfaced, not yet mitigated',
47
+ mitigated: 'Known unknown — mitigated with partial coverage',
48
+ resolved: 'Known unknown — fully addressed'
49
+ }.freeze
50
+
51
+ def self.label_for(labels, value)
52
+ match = labels.find { |range, _| range.cover?(value) }
53
+ match&.last
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveBlindspot
8
+ module Helpers
9
+ class KnowledgeBoundary
10
+ include Constants
11
+
12
+ attr_reader :id, :domain, :confidence, :coverage_estimate, :created_at, :updated_at
13
+
14
+ def initialize(domain:, confidence: 0.5, coverage_estimate: 0.5)
15
+ @id = SecureRandom.uuid
16
+ @domain = domain.to_sym
17
+ @confidence = confidence.to_f.clamp(0.0, 1.0)
18
+ @coverage_estimate = coverage_estimate.to_f.clamp(0.0, 1.0)
19
+ @created_at = Time.now.utc
20
+ @updated_at = Time.now.utc
21
+ end
22
+
23
+ def coverage_label
24
+ Constants.label_for(COVERAGE_LABELS, @coverage_estimate) || :minimal
25
+ end
26
+
27
+ def gap_detected?(error_occurred: false)
28
+ error_occurred && @confidence >= AWARENESS_THRESHOLD
29
+ end
30
+
31
+ def update_confidence!(new_confidence)
32
+ @confidence = new_confidence.to_f.clamp(0.0, 1.0)
33
+ @updated_at = Time.now.utc
34
+ self
35
+ end
36
+
37
+ def update_coverage!(new_coverage)
38
+ @coverage_estimate = new_coverage.to_f.clamp(0.0, 1.0)
39
+ @updated_at = Time.now.utc
40
+ self
41
+ end
42
+
43
+ def to_h
44
+ {
45
+ id: @id,
46
+ domain: @domain,
47
+ confidence: @confidence,
48
+ coverage_estimate: @coverage_estimate,
49
+ coverage_label: coverage_label,
50
+ created_at: @created_at,
51
+ updated_at: @updated_at
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveBlindspot
6
+ module Runners
7
+ module CognitiveBlindspot
8
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+
10
+ def register_blindspot(domain:, discovered_by:, description:,
11
+ severity: nil, **)
12
+ sev = severity || Helpers::Constants::DEFAULT_SEVERITY
13
+ spot = engine.register_blindspot(
14
+ domain: domain,
15
+ discovered_by: discovered_by,
16
+ description: description,
17
+ severity: sev
18
+ )
19
+ { success: true }.merge(spot.to_h)
20
+ end
21
+
22
+ def acknowledge_blindspot(blindspot_id:, **)
23
+ result = engine.acknowledge_blindspot(blindspot_id: blindspot_id)
24
+ return { success: false, error: 'blindspot not found' } unless result[:found]
25
+
26
+ { success: true }.merge(result)
27
+ end
28
+
29
+ def mitigate_blindspot(blindspot_id:, boost: nil, **)
30
+ b = boost || Helpers::Constants::SEVERITY_BOOST
31
+ result = engine.mitigate_blindspot(blindspot_id: blindspot_id, boost: b)
32
+ return { success: false, error: 'blindspot not found' } unless result[:found]
33
+
34
+ { success: true }.merge(result)
35
+ end
36
+
37
+ def resolve_blindspot(blindspot_id:, **)
38
+ result = engine.resolve_blindspot(blindspot_id: blindspot_id)
39
+ return { success: false, error: 'blindspot not found' } unless result[:found]
40
+
41
+ { success: true }.merge(result)
42
+ end
43
+
44
+ def set_knowledge_boundary(domain:, confidence: 0.5, coverage_estimate: 0.5, **)
45
+ boundary = engine.set_boundary(
46
+ domain: domain,
47
+ confidence: confidence,
48
+ coverage_estimate: coverage_estimate
49
+ )
50
+ { success: true }.merge(boundary.to_h)
51
+ end
52
+
53
+ def detect_boundary_gap(domain:, error_occurred: false, **)
54
+ result = engine.detect_boundary_gap(domain: domain, error_occurred: error_occurred)
55
+ { success: true }.merge(result)
56
+ end
57
+
58
+ def active_blindspots_report(**)
59
+ spots = engine.active_blindspots
60
+ { success: true, count: spots.size, blindspots: spots.map(&:to_h) }
61
+ end
62
+
63
+ def most_severe_report(limit: 5, **)
64
+ spots = engine.most_severe(limit: limit)
65
+ { success: true, limit: limit, blindspots: spots.map(&:to_h) }
66
+ end
67
+
68
+ def mitigation_strategies_report(domain: nil, **)
69
+ strategies = engine.mitigation_strategies(domain: domain)
70
+ { success: true, count: strategies.size, strategies: strategies }
71
+ end
72
+
73
+ def coverage_report(**)
74
+ boundaries = engine.coverage_report
75
+ { success: true, count: boundaries.size, boundaries: boundaries }
76
+ end
77
+
78
+ def johari_report(**)
79
+ engine.johari_report
80
+ end
81
+
82
+ def awareness_score_report(**)
83
+ score = engine.awareness_score
84
+ {
85
+ success: true,
86
+ awareness_score: score.round(10),
87
+ awareness_label: engine.awareness_label,
88
+ awareness_gap: engine.awareness_gap
89
+ }
90
+ end
91
+
92
+ def blindspot_stats(**)
93
+ engine.to_h
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveBlindspot
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cognitive_blindspot/version'
4
+ require_relative 'cognitive_blindspot/helpers/constants'
5
+ require_relative 'cognitive_blindspot/helpers/blindspot'
6
+ require_relative 'cognitive_blindspot/helpers/knowledge_boundary'
7
+ require_relative 'cognitive_blindspot/helpers/blindspot_engine'
8
+ require_relative 'cognitive_blindspot/runners/cognitive_blindspot'
9
+ require_relative 'cognitive_blindspot/client'
10
+
11
+ module Legion
12
+ module Extensions
13
+ module CognitiveBlindspot
14
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-blindspot
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: Johari Window for AI — tracks unknown-unknowns, manages knowledge boundaries,
27
+ and scores agent awareness with blindspot acknowledgement and mitigation strategies.
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/ci.yml"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CLAUDE.md
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - README.md
42
+ - lex-cognitive-blindspot.gemspec
43
+ - lib/legion/extensions/cognitive_blindspot.rb
44
+ - lib/legion/extensions/cognitive_blindspot/client.rb
45
+ - lib/legion/extensions/cognitive_blindspot/helpers/blindspot.rb
46
+ - lib/legion/extensions/cognitive_blindspot/helpers/blindspot_engine.rb
47
+ - lib/legion/extensions/cognitive_blindspot/helpers/constants.rb
48
+ - lib/legion/extensions/cognitive_blindspot/helpers/knowledge_boundary.rb
49
+ - lib/legion/extensions/cognitive_blindspot/runners/cognitive_blindspot.rb
50
+ - lib/legion/extensions/cognitive_blindspot/version.rb
51
+ homepage: https://github.com/LegionIO/lex-cognitive-blindspot
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-blindspot
56
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-blindspot
57
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-blindspot/blob/main/README.md
58
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-blindspot/blob/main/CHANGELOG.md
59
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-blindspot/issues
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: Cognitive blindspot detection for LegionIO agents
78
+ test_files: []