promptmenot 0.1.1

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: 000e08f45a4388639584116f108d8e5d9f94f8e2d606e1f1823c82fb67645cf7
4
+ data.tar.gz: 0460fc97672f78cd0f16a67b0ea10139bbcc73b477eef6ab47d19802636cbc1d
5
+ SHA512:
6
+ metadata.gz: 8d922773aa2f10bc810bdf285a7d28bea76817ad57fdddb49b69168b0e573b2c3e1b78f9d045d457279843ee3850d4024d480997aa1bd48392cbbca002a69124
7
+ data.tar.gz: 7158d333c28534f1f6d49a21652497ac765c60b7df39c4a49d989dc0e97a55d38c620e84e54eeae5f85af0a858ecd56b84ca83dc0e090172687ed1f0b30e8187
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,36 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ Style/FrozenStringLiteralComment:
10
+ EnforcedStyle: always
11
+
12
+ Metrics/MethodLength:
13
+ Max: 20
14
+
15
+ Metrics/ClassLength:
16
+ Max: 150
17
+
18
+ Metrics/BlockLength:
19
+ Exclude:
20
+ - "spec/**/*"
21
+ - "promptmenot.gemspec"
22
+
23
+ Layout/LineLength:
24
+ Max: 120
25
+ Exclude:
26
+ - "lib/promptmenot/patterns/**/*"
27
+
28
+ Metrics/AbcSize:
29
+ Exclude:
30
+ - "lib/promptmenot/detector.rb"
31
+
32
+ Style/StringLiterals:
33
+ EnforcedStyle: double_quotes
34
+
35
+ Style/StringLiteralsInInterpolation:
36
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-02-17
4
+
5
+ ### Added
6
+
7
+ - Core detection engine with 6 pattern categories (~60 patterns)
8
+ - Direct instruction override
9
+ - Role manipulation
10
+ - Delimiter injection
11
+ - Encoding obfuscation
12
+ - Indirect injection
13
+ - Context manipulation
14
+ - Filter-based sensitivity levels: `:low`, `:medium`, `:high`, `:paranoid`
15
+ - Two operating modes: `:reject` (validation error) and `:sanitize` (strip content)
16
+ - ActiveModel validator (`prompt_safety`)
17
+ - Standalone API (`Promptmenot.safe?`, `.detect`, `.sanitize`)
18
+ - Global configuration DSL with custom pattern support
19
+ - Detection callbacks
20
+ - Rails generator (`rails g promptmenot:install`)
21
+ - I18n support for error messages
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,69 @@
1
+ # Contributing to PromptMeNot
2
+
3
+ We'd love your help improving PromptMeNot! Here's how to contribute:
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/kevinl05/promptmenot.git
9
+ cd promptmenot
10
+ bundle install
11
+ ```
12
+
13
+ ## Running Tests
14
+
15
+ ```bash
16
+ # Run full test suite
17
+ bundle exec rspec
18
+
19
+ # Run specific test file
20
+ bundle exec rspec spec/promptmenot/detector_spec.rb
21
+
22
+ # Run with coverage
23
+ bundle exec rspec --coverage
24
+ ```
25
+
26
+ ## Code Quality
27
+
28
+ ```bash
29
+ # Run RuboCop linter
30
+ bundle exec rubocop
31
+
32
+ # Auto-fix offenses
33
+ bundle exec rubocop -a
34
+ ```
35
+
36
+ ## Making Changes
37
+
38
+ 1. **Fork** the repository on GitHub
39
+ 2. **Create a branch** for your feature: `git checkout -b feature/my-feature`
40
+ 3. **Make your changes** and add tests
41
+ 4. **Ensure all tests pass**: `bundle exec rspec`
42
+ 5. **Ensure code is clean**: `bundle exec rubocop -a`
43
+ 6. **Commit** with clear messages: `git commit -am 'Add new pattern for X'`
44
+ 7. **Push** to your fork: `git push origin feature/my-feature`
45
+ 8. **Open a PR** on GitHub
46
+
47
+ ## Adding New Patterns
48
+
49
+ New injection attack patterns go in `lib/promptmenot/patterns/`.
50
+
51
+ See existing pattern files for the DSL. Each pattern registers with:
52
+ - `name` — unique identifier
53
+ - `regex` — detection pattern
54
+ - `sensitivity` — `:low`, `:medium`, `:high`, or `:paranoid`
55
+ - `confidence` — `:high`, `:medium`, or `:low`
56
+
57
+ Always include tests in `spec/promptmenot/patterns/`.
58
+
59
+ ## Reporting Issues
60
+
61
+ Found a bug or have a suggestion? Open an issue on GitHub with:
62
+ - Clear description of the problem
63
+ - Steps to reproduce (if applicable)
64
+ - Expected vs. actual behavior
65
+ - Ruby/Rails version info
66
+
67
+ ## License
68
+
69
+ All contributions are made under the MIT license.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rake", "~> 13.0"
8
+ gem "rspec", "~> 3.0"
9
+ gem "rubocop", "~> 1.21", require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 promptmenot contributors
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # PromptMeNot
2
+
3
+ Detect and sanitize prompt injection attacks in user-submitted text. Protects Rails apps against:
4
+
5
+ - **Direct injection** -- users trying to hack your LLMs via form inputs
6
+ - **Indirect injection** -- users storing malicious prompts in profiles so other LLMs that scrape your site get compromised
7
+
8
+ ## Installation
9
+
10
+ Add to your Gemfile:
11
+
12
+ ```ruby
13
+ gem "promptmenot"
14
+ ```
15
+
16
+ Then run:
17
+
18
+ ```bash
19
+ bundle install
20
+ rails generate promptmenot:install # creates config/initializers/promptmenot.rb
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### ActiveModel Validation
26
+
27
+ ```ruby
28
+ class UserProfile < ApplicationRecord
29
+ # Reject mode (default) -- adds validation error
30
+ validates :bio, prompt_safety: true
31
+
32
+ # Sanitize mode -- strips malicious content, no error
33
+ validates :about_me, prompt_safety: { mode: :sanitize }
34
+
35
+ # Custom sensitivity
36
+ validates :notes, prompt_safety: { sensitivity: :high, mode: :reject }
37
+ end
38
+ ```
39
+
40
+ ### Standalone API
41
+
42
+ ```ruby
43
+ Promptmenot.safe?("Hello world")
44
+ # => true
45
+
46
+ Promptmenot.safe?("Ignore all previous instructions")
47
+ # => false
48
+
49
+ result = Promptmenot.detect("Some text with [SYSTEM] override")
50
+ result.safe? # => false
51
+ result.unsafe? # => true
52
+ result.matches # => [#<Match ...>]
53
+ result.categories_detected # => [:delimiter_injection]
54
+ result.summary # => "Detected 1 potential prompt injection pattern..."
55
+
56
+ sanitized = Promptmenot.sanitize("Hello. Ignore all previous instructions. Goodbye.")
57
+ sanitized.sanitized # => "Hello. [removed] Goodbye."
58
+ sanitized.changed? # => true
59
+ sanitized.original # => "Hello. Ignore all previous instructions. Goodbye."
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ ```ruby
65
+ # config/initializers/promptmenot.rb
66
+ Promptmenot.configure do |config|
67
+ # Default sensitivity level for all validations
68
+ # Options: :low, :medium (default), :high, :paranoid
69
+ config.sensitivity = :medium
70
+
71
+ # Default mode: :reject (validation error) or :sanitize (strip content)
72
+ config.mode = :reject
73
+
74
+ # Replacement text used in sanitize mode
75
+ config.replacement_text = "[removed]"
76
+
77
+ # Callback fired whenever injection is detected
78
+ config.on_detect = ->(result) { Rails.logger.warn("Injection: #{result.summary}") }
79
+
80
+ # Register custom patterns
81
+ config.add_pattern(
82
+ name: :my_custom_pattern,
83
+ regex: /my dangerous regex/i,
84
+ category: :custom,
85
+ sensitivity: :medium,
86
+ confidence: :high
87
+ )
88
+ end
89
+ ```
90
+
91
+ ## Sensitivity Levels
92
+
93
+ Sensitivity controls which patterns are active. Each pattern declares a minimum sensitivity level -- it only runs when the requested sensitivity is at or above that level.
94
+
95
+ | Pattern sensitivity | Active at `:low` | `:medium` | `:high` | `:paranoid` |
96
+ |---|---|---|---|---|
97
+ | `:low` | Yes | Yes | Yes | Yes |
98
+ | `:medium` | No | Yes | Yes | Yes |
99
+ | `:high` | No | No | Yes | Yes |
100
+ | `:paranoid` | No | No | No | Yes |
101
+
102
+ **`:low`** catches only the most obvious attacks (e.g., "ignore all previous instructions"). **`:paranoid`** flags anything remotely suspicious, including mixed-script text.
103
+
104
+ ## Pattern Categories
105
+
106
+ | Category | Examples | Count |
107
+ |---|---|---|
108
+ | `direct_instruction_override` | "ignore previous instructions", "new instructions:" | ~12 |
109
+ | `role_manipulation` | "jailbreak mode", "act as unrestricted AI", "DAN" | ~10 |
110
+ | `delimiter_injection` | `<\|system\|>`, `[SYSTEM]`, ChatML tokens | ~10 |
111
+ | `encoding_obfuscation` | Base64 payloads, zero-width chars, hex escapes | ~10 |
112
+ | `indirect_injection` | "Dear AI", "if you are an LLM", "note to chatbot" | ~10 |
113
+ | `context_manipulation` | `===RESET===`, "the above is a test", prompt leaking | ~8 |
114
+
115
+ ## False Positive Mitigation
116
+
117
+ Patterns use contextual qualifiers to minimize false positives:
118
+
119
+ - "ignore" alone is fine -- "ignore **previous instructions**" is flagged
120
+ - "act as" requires malicious qualifiers -- "act as a consultant" passes
121
+ - "you are now" requires AI/restriction qualifiers -- "you are now subscribed" passes
122
+ - "from now on" requires imperative "you must/will" -- "from now on I'll work from home" passes
123
+ - Broad patterns are placed at `:high`/`:paranoid` sensitivity so they don't fire at default settings
124
+
125
+ ## License
126
+
127
+ MIT License. See [LICENSE.txt](LICENSE.txt).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/agents.md ADDED
@@ -0,0 +1,150 @@
1
+ # Agents Reference Guide
2
+
3
+ This document provides operational knowledge for AI agents and developers working on PromptMeNot.
4
+
5
+ ## Project Overview
6
+
7
+ - **Project Name:** PromptMeNot
8
+ - **Type:** Ruby gem (Rails plugin)
9
+ - **Framework:** ActiveModel / ActiveSupport (>= 6.0)
10
+ - **Language:** Ruby >= 3.0
11
+ - **Package Manager:** Bundler
12
+ - **Test Framework:** RSpec
13
+ - **Linter:** RuboCop
14
+
15
+ ---
16
+
17
+ ## Getting Started
18
+
19
+ ```bash
20
+ # Install dependencies
21
+ bundle install
22
+
23
+ # Run tests
24
+ bundle exec rspec
25
+
26
+ # Run linter
27
+ bundle exec rubocop
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Build & Release
33
+
34
+ ### Build the Gem
35
+
36
+ ```bash
37
+ # Build .gem file
38
+ gem build promptmenot.gemspec
39
+
40
+ # Install locally for testing
41
+ gem install promptmenot-*.gem
42
+ ```
43
+
44
+ ### Release
45
+
46
+ ```bash
47
+ # Bump version in lib/promptmenot/version.rb, then:
48
+ gem build promptmenot.gemspec
49
+ gem push promptmenot-*.gem
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Architecture
55
+
56
+ ### Pattern System
57
+
58
+ Patterns are registered via a DSL in `lib/promptmenot/patterns/*.rb`. Each pattern declares:
59
+ - **name** — unique identifier
60
+ - **category** — which pattern category it belongs to
61
+ - **regex** — the detection regex
62
+ - **sensitivity** — minimum sensitivity level to activate (`:low`, `:medium`, `:high`, `:paranoid`)
63
+ - **confidence** — how confident the match is (`:high`, `:medium`, `:low`)
64
+
65
+ ### Sensitivity Levels (Filter-Based)
66
+
67
+ Each pattern declares its minimum sensitivity. At runtime, only patterns at or below the requested level are active:
68
+
69
+ | Pattern sensitivity | Active at :low | :medium | :high | :paranoid |
70
+ |---|---|---|---|---|
71
+ | :low | Yes | Yes | Yes | Yes |
72
+ | :medium | No | Yes | Yes | Yes |
73
+ | :high | No | No | Yes | Yes |
74
+ | :paranoid | No | No | No | Yes |
75
+
76
+ ### Detection Flow
77
+
78
+ 1. `Detector` receives text + sensitivity level
79
+ 2. `PatternRegistry` filters patterns by sensitivity
80
+ 3. Each pattern's regex runs against the text
81
+ 4. Overlapping matches are deduplicated
82
+ 5. `Result` object is returned (safe?/unsafe?, matches, categories)
83
+
84
+ ### Modes
85
+
86
+ - **reject** — adds ActiveModel validation error (default)
87
+ - **sanitize** — strips matched content from the field value
88
+
89
+ ---
90
+
91
+ ## Common Scripts Reference
92
+
93
+ ```bash
94
+ # Development
95
+ bundle install # Install dependencies
96
+ bundle console # Open IRB with gem loaded (if configured)
97
+
98
+ # Testing
99
+ bundle exec rspec # Run full test suite
100
+ bundle exec rspec spec/promptmenot/detector_spec.rb # Run single spec
101
+
102
+ # Linting
103
+ bundle exec rubocop # Run linter
104
+ bundle exec rubocop -a # Auto-fix offenses
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Key File Locations
110
+
111
+ | File | Purpose |
112
+ |---|---|
113
+ | `lib/promptmenot.rb` | Root entry point, convenience API |
114
+ | `lib/promptmenot/version.rb` | Gem version |
115
+ | `lib/promptmenot/configuration.rb` | Global config DSL |
116
+ | `lib/promptmenot/detector.rb` | Core detection engine |
117
+ | `lib/promptmenot/sanitizer.rb` | Content sanitization |
118
+ | `lib/promptmenot/validator.rb` | ActiveModel validator |
119
+ | `lib/promptmenot/pattern_registry.rb` | Central pattern registry |
120
+ | `lib/promptmenot/patterns/` | All pattern category definitions |
121
+ | `lib/promptmenot/railtie.rb` | Rails auto-config |
122
+ | `config/locales/en.yml` | I18n error messages |
123
+ | `promptmenot.gemspec` | Gem specification |
124
+ | `spec/` | All test specs |
125
+
126
+ ---
127
+
128
+ ## Troubleshooting
129
+
130
+ ### Bundle Install Fails
131
+
132
+ **Symptom:** Dependency resolution errors
133
+
134
+ ```bash
135
+ # Remove lockfile and retry
136
+ rm Gemfile.lock && bundle install
137
+ ```
138
+
139
+ ### RSpec Can't Find Patterns
140
+
141
+ **Symptom:** Tests pass but no patterns are detected
142
+
143
+ Check that all pattern files in `lib/promptmenot/patterns/` are required in `lib/promptmenot.rb`.
144
+
145
+ ---
146
+
147
+ ## Related Documentation
148
+
149
+ - `README.md` - Usage examples, configuration guide, pattern reference
150
+ - `CHANGELOG.md` - Version history
@@ -0,0 +1,4 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ prompt_injection_detected: "contains potentially unsafe prompt injection content"
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Promptmenot
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Creates a Promptmenot initializer in your application."
11
+
12
+ def copy_initializer
13
+ template "promptmenot.rb", "config/initializers/promptmenot.rb"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Promptmenot.configure do |config|
4
+ # Default sensitivity level for all validations.
5
+ # Options: :low, :medium (default), :high, :paranoid
6
+ # config.sensitivity = :medium
7
+
8
+ # Default mode for the prompt_safety validator.
9
+ # :reject — adds a validation error (default)
10
+ # :sanitize — strips matched content from the field
11
+ # config.mode = :reject
12
+
13
+ # Replacement text used in sanitize mode.
14
+ # config.replacement_text = "[removed]"
15
+
16
+ # Callback fired whenever an injection is detected.
17
+ # config.on_detect = ->(result) { Rails.logger.warn("Prompt injection: #{result.summary}") }
18
+
19
+ # Register custom patterns:
20
+ # config.add_pattern(
21
+ # name: :my_custom_pattern,
22
+ # regex: /my custom regex/i,
23
+ # category: :custom,
24
+ # sensitivity: :medium,
25
+ # confidence: :high
26
+ # )
27
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Promptmenot
4
+ class Configuration
5
+ VALID_SENSITIVITIES = %i[low medium high paranoid].freeze
6
+ VALID_MODES = %i[reject sanitize].freeze
7
+
8
+ attr_reader :sensitivity, :mode
9
+ attr_accessor :replacement_text, :on_detect, :max_length
10
+
11
+ def initialize
12
+ @sensitivity = :medium
13
+ @mode = :reject
14
+ @replacement_text = "[removed]"
15
+ @max_length = 50_000
16
+ @custom_patterns_list = []
17
+ @custom_patterns = nil
18
+ @on_detect = nil
19
+ end
20
+
21
+ def sensitivity=(value)
22
+ sym = value.to_sym
23
+ unless VALID_SENSITIVITIES.include?(sym)
24
+ raise ConfigurationError, "Invalid sensitivity: #{value}. Must be one of: #{VALID_SENSITIVITIES.join(", ")}"
25
+ end
26
+
27
+ @sensitivity = sym
28
+ end
29
+
30
+ def mode=(value)
31
+ sym = value.to_sym
32
+ unless VALID_MODES.include?(sym)
33
+ raise ConfigurationError, "Invalid mode: #{value}. Must be one of: #{VALID_MODES.join(", ")}"
34
+ end
35
+
36
+ @mode = sym
37
+ end
38
+
39
+ def custom_patterns
40
+ @custom_patterns ||= @custom_patterns_list.dup.freeze
41
+ end
42
+
43
+ def add_pattern(name:, regex:, category: :custom, sensitivity: :medium, confidence: :medium)
44
+ @custom_patterns_list << Pattern.new(
45
+ name: name,
46
+ category: category,
47
+ regex: regex,
48
+ sensitivity: sensitivity,
49
+ confidence: confidence
50
+ )
51
+ @custom_patterns = nil
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Promptmenot
4
+ class Detector
5
+ attr_reader :sensitivity, :categories
6
+
7
+ def initialize(sensitivity: nil, categories: nil)
8
+ @sensitivity = sensitivity || Promptmenot.configuration.sensitivity
9
+ @categories = categories
10
+ end
11
+
12
+ def detect(text)
13
+ return Result.new(text: text.to_s) if text.nil? || text.to_s.strip.empty?
14
+
15
+ input = text.to_s
16
+ max = Promptmenot.configuration.max_length
17
+ input = input[0, max] if max && input.length > max
18
+
19
+ patterns = Promptmenot.registry.for_sensitivity_and_categories(
20
+ @sensitivity,
21
+ categories: @categories
22
+ )
23
+
24
+ all_matches = patterns.flat_map { |pattern| pattern.match(input) }
25
+ deduped = deduplicate(all_matches)
26
+
27
+ result = Result.new(text: input, matches: deduped)
28
+ fire_callback(result) if result.unsafe?
29
+ result
30
+ end
31
+
32
+ private
33
+
34
+ def deduplicate(matches)
35
+ return matches if matches.size <= 1
36
+
37
+ sorted = matches.sort_by { |m| [m.position.begin, -m.position.size] }
38
+ kept = []
39
+
40
+ sorted.each do |match|
41
+ existing = kept.find { |m| overlaps?(m, match) }
42
+ if existing
43
+ # Keep the larger match when overlapping
44
+ if match.position.size > existing.position.size
45
+ kept.delete(existing)
46
+ kept << match
47
+ end
48
+ else
49
+ kept << match
50
+ end
51
+ end
52
+
53
+ kept
54
+ end
55
+
56
+ def overlaps?(first, second)
57
+ first.position.begin < second.position.end && second.position.begin < first.position.end
58
+ end
59
+
60
+ def fire_callback(result)
61
+ callback = Promptmenot.configuration.on_detect
62
+ callback&.call(result)
63
+ rescue StandardError => e
64
+ warn "[Promptmenot] on_detect callback raised #{e.class}: #{e.message}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Promptmenot
4
+ class Error < StandardError; end
5
+ class ConfigurationError < Error; end
6
+ class PatternError < Error; end
7
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Promptmenot
4
+ class Match
5
+ attr_reader :pattern, :matched_text, :position
6
+
7
+ def initialize(pattern:, matched_text:, position:)
8
+ @pattern = pattern
9
+ @matched_text = matched_text
10
+ @position = position
11
+ end
12
+
13
+ def category
14
+ pattern.category
15
+ end
16
+
17
+ def pattern_name
18
+ pattern.name
19
+ end
20
+
21
+ def confidence
22
+ pattern.confidence
23
+ end
24
+
25
+ def sensitivity
26
+ pattern.sensitivity
27
+ end
28
+
29
+ def ==(other)
30
+ other.is_a?(Match) &&
31
+ pattern_name == other.pattern_name &&
32
+ matched_text == other.matched_text &&
33
+ position == other.position
34
+ end
35
+ end
36
+ end