nagori-fsrs 0.1.0-x86_64-linux

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: 302362c037692cd7f93735fff5006eef832e014f55395302ff30f96d70adaabe
4
+ data.tar.gz: 1feeafec2ace2dee429a15095dffa08e5c2bfb1159fcd7b67965f7371714a787
5
+ SHA512:
6
+ metadata.gz: 6dca3dd247c4db075346f5a9e2a60ddb28e77433cbb4867de0bbcb3da71ccdc1f362eaa7fc92680014f7d418140c82065e59f2079ba4bbf9d3090eaaad791424
7
+ data.tar.gz: 3982242047ccfde18eac3e5351421c9e3c3cdaddd01b97e53522de3f8371708391036c35ace372300bed1c6301f8c0de97253932bc71c741e3cd6545b1fb53d2
data/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, David Afonso (Nagori Ruby bindings)
4
+ Copyright (c) 2023, Open Spaced Repetition (fsrs-rs, the wrapped `fsrs` crate)
5
+
6
+ Nagori is a Ruby binding for fsrs-rs (https://github.com/open-spaced-repetition/fsrs-rs),
7
+ which is distributed under the BSD 3-Clause License. This gem redistributes and
8
+ links against that crate; its copyright notice is retained above as required.
9
+
10
+ Redistribution and use in source and binary forms, with or without
11
+ modification, are permitted provided that the following conditions are met:
12
+
13
+ 1. Redistributions of source code must retain the above copyright notice, this
14
+ list of conditions and the following disclaimer.
15
+
16
+ 2. Redistributions in binary form must reproduce the above copyright notice,
17
+ this list of conditions and the following disclaimer in the documentation
18
+ and/or other materials provided with the distribution.
19
+
20
+ 3. Neither the name of the copyright holder nor the names of its
21
+ contributors may be used to endorse or promote products derived from
22
+ this software without specific prior written permission.
23
+
24
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
28
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Nagori 名残
2
+
3
+ Ruby bindings for [fsrs-rs](https://github.com/open-spaced-repetition/fsrs-rs)
4
+ (the [`fsrs`](https://crates.io/crates/fsrs) crate) — the **FSRS-6** spaced-
5
+ repetition scheduler, optimizer, and simulator, in Rust.
6
+
7
+ Nagori is a thin, idiomatic wrapper: plain Ruby values in and out (hashes,
8
+ arrays, keyword args), the number crunching in Rust. Long-running calls
9
+ (`compute_parameters`, `evaluate`, `simulate`, `optimal_retention`) release the
10
+ GVL so they don't block other threads.
11
+
12
+ Built the same way as [kabosu](https://github.com/davafons/kabosu):
13
+ [rb_sys](https://github.com/oxidize-rb/rb-sys) + [magnus](https://github.com/matsadler/magnus),
14
+ precompiled for the usual platform matrix.
15
+
16
+ ## Install
17
+
18
+ ```ruby
19
+ # Gemfile
20
+ gem "nagori-fsrs"
21
+ ```
22
+
23
+ During development against a checkout:
24
+
25
+ ```ruby
26
+ gem "nagori-fsrs", path: "../nagori-fsrs"
27
+ ```
28
+
29
+ ## Concepts
30
+
31
+ - **Rating** is `1 = Again`, `2 = Hard`, `3 = Good`, `4 = Easy` (symbols
32
+ `:again/:hard/:good/:easy` are accepted anywhere a rating is taken).
33
+ - **A review** is `{ rating:, delta_t: }`, where `delta_t` is **whole days since
34
+ the previous review**, `0` for the first review and for same-day re-reviews.
35
+ - **Intervals** come back as **fractional, unrounded `f32` days**. Nagori never
36
+ rounds — rounding, fuzz, and load-balancing are application policy.
37
+ - **Parameters** are the FSRS weight vector. `nil`/empty uses
38
+ `Nagori::DEFAULT_PARAMETERS`; 17- (FSRS-4.5) and 19-length (FSRS-5) vectors are
39
+ accepted and padded to the 21-length FSRS-6 shape.
40
+
41
+ ## Scheduling
42
+
43
+ ```ruby
44
+ require "nagori-fsrs"
45
+
46
+ fsrs = Nagori::FSRS.new # default parameters
47
+ # fsrs = Nagori::FSRS.new(my_21_floats) # or a trained vector
48
+
49
+ # The four answer buttons for a brand-new card (nil memory state):
50
+ fsrs.next_states(nil, 0.9, 0)
51
+ # => { again: { stability: 0.212, difficulty: 6.413, interval: 0.212 },
52
+ # hard: { ... }, good: { ... }, easy: { ... } }
53
+
54
+ # For a card with an existing memory state, 5 days elapsed:
55
+ fsrs.next_states({ stability: 10.0, difficulty: 5.0 }, 0.9, 5)
56
+ ```
57
+
58
+ `next_states(memory_state, desired_retention, days_elapsed)` returns a hash of
59
+ the four buttons; each is `{ stability:, difficulty:, interval: }`.
60
+
61
+ ## Replaying history → memory state
62
+
63
+ ```ruby
64
+ reviews = [
65
+ { rating: 3, delta_t: 0 }, # first review, same day
66
+ { rating: 3, delta_t: 5 },
67
+ { rating: 4, delta_t: 20 }
68
+ ]
69
+ fsrs.memory_state(reviews) # => { stability:, difficulty: }
70
+
71
+ # Batch (Anki import / FSRS-6 migration):
72
+ fsrs.memory_state_batch([reviews, other_reviews])
73
+ # => [{ stability:, difficulty: }, ...]
74
+
75
+ # No revlog, only SM-2 values:
76
+ fsrs.memory_state_from_sm2(2.5, 10.0, 0.9) # ease, interval, retention
77
+ ```
78
+
79
+ ## Intervals & retrievability
80
+
81
+ ```ruby
82
+ fsrs.next_interval(nil, 0.9, 3) # new card, Good -> fractional days
83
+ fsrs.next_interval(100.0, 0.9, 3) # from a known stability
84
+
85
+ Nagori.current_retrievability({ stability: 10.0, difficulty: 5.0 }, 5.0)
86
+ # decay defaults to Nagori::FSRS6_DEFAULT_DECAY; pass a third arg to override
87
+ ```
88
+
89
+ ## Optimizer
90
+
91
+ `compute_parameters` and `evaluate` take a **training set**: one item per
92
+ review, each item a *prefix* of a card's history (all prefixes of length ≥ 2).
93
+ fsrs-rs does not expand histories for you — pretraining keys off the
94
+ exactly-one-long-term-review prefixes.
95
+
96
+ ```ruby
97
+ # items: Array of Array-of-{rating:, delta_t:}
98
+ params = Nagori.compute_parameters(items, enable_short_term: true)
99
+ # => 21 floats (GVL released during training)
100
+
101
+ # Health check: does a candidate parameter set fit the data?
102
+ Nagori::FSRS.new(params).evaluate(items) # => { log_loss:, rmse_bins: }
103
+ ```
104
+
105
+ Typical apply-only-if-better gate:
106
+
107
+ ```ruby
108
+ old_fit = Nagori::FSRS.new(current_params).evaluate(items)
109
+ new_fit = Nagori::FSRS.new(candidate_params).evaluate(items)
110
+ apply = new_fit[:log_loss] < old_fit[:log_loss]
111
+ ```
112
+
113
+ ## Simulator
114
+
115
+ ```ruby
116
+ # Project reviews/day and memorized count over a horizon:
117
+ Nagori.simulate(Nagori::DEFAULT_PARAMETERS, 0.9,
118
+ config: { learn_span: 90, deck_size: 1000 }, seed: 42)
119
+ # => { memorized_cnt_per_day:, review_cnt_per_day:, learn_cnt_per_day:,
120
+ # cost_per_day:, correct_cnt_per_day:, introduced_cnt_per_day:,
121
+ # average_desired_retention: }
122
+
123
+ # Retention-slider preview (expected daily seconds of work):
124
+ Nagori.expected_workload(params, 0.95, config: { deck_size: 1000 })
125
+
126
+ # Suggested retention (CMRR); GVL released:
127
+ Nagori.optimal_retention(params, config: { learn_span: 365 })
128
+
129
+ # Calibrate a config from Anki-style revlog rows:
130
+ config = Nagori.extract_simulator_config(revlog_entries, day_cutoff)
131
+ Nagori.simulate(params, 0.9, config: config)
132
+ ```
133
+
134
+ `config` is a plain hash; any omitted key falls back to fsrs-rs's default
135
+ `SimulatorConfig`. The closure hooks (`post_scheduling_fn`,
136
+ `review_priority_fn`) are intentionally not exposed. Each revlog entry is a hash
137
+ with `:id, :cid, :usn, :button_chosen, :interval, :last_interval, :ease_factor,
138
+ :taken_millis, :review_kind` (review_kind `0..4`).
139
+
140
+ ## Errors
141
+
142
+ Invalid input (bad parameter length, rating outside `1..4`, negative `delta_t`,
143
+ malformed memory state) raises `ArgumentError`. Computation failures surface as
144
+ `RuntimeError`.
145
+
146
+ ## Version pinning
147
+
148
+ The wrapped crate is pinned exactly (`fsrs = "=6.6.x"` in
149
+ `ext/nagori/Cargo.toml`) because a crate bump can shift scheduler outputs. The
150
+ gem's golden tests assert exact stability/difficulty/interval values, so a bump
151
+ that changes results fails loudly. Bump the pin and the golden vectors together.
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ bundle install
157
+ bundle exec rake compile
158
+ bundle exec rake test
159
+ ```
160
+
161
+ Requires a Rust toolchain (stable). Tests are Minitest with golden vectors
162
+ generated from fsrs-rs itself.
163
+
164
+ ## License & attribution
165
+
166
+ Nagori is released under the **BSD 3-Clause License**. It wraps and
167
+ redistributes [fsrs-rs](https://github.com/open-spaced-repetition/fsrs-rs)
168
+ (© 2023 Open Spaced Repetition), also BSD 3-Clause. See [LICENSE](LICENSE) for
169
+ both copyright notices.
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ module Nagori
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,2 @@
1
+ # Bundler auto-requires the gem name; the code lives under lib/nagori.
2
+ require_relative "nagori"
data/lib/nagori.rb ADDED
@@ -0,0 +1,233 @@
1
+ require_relative "nagori/version"
2
+
3
+ # Load the native extension. Precompiled gems place it under a per-Ruby-version
4
+ # subdirectory (lib/nagori/3.3/nagori.bundle, etc.); a source build leaves it at
5
+ # lib/nagori/nagori.{bundle,so}. Try the version-suffixed path first, fall back
6
+ # to the flat path so dev/source builds keep working.
7
+ begin
8
+ major_minor = RUBY_VERSION.split(".").take(2).join(".")
9
+ require_relative "nagori/#{major_minor}/nagori"
10
+ rescue LoadError
11
+ require_relative "nagori/nagori"
12
+ end
13
+
14
+ # Ruby bindings for fsrs-rs (the `fsrs` crate), FSRS-6.
15
+ #
16
+ # Plain values in and out: reviews are `{ rating:, delta_t: }` hashes (rating
17
+ # 1=Again 2=Hard 3=Good 4=Easy; delta_t = whole days since the previous review,
18
+ # 0 for the first review and same-day re-reviews). Intervals come back as
19
+ # fractional, unrounded f32 days — rounding/fuzz policy lives in the caller.
20
+ module Nagori
21
+ class Error < StandardError; end
22
+
23
+ # The 21-float FSRS-6 default weights (fallback when a user has no trained
24
+ # parameters yet).
25
+ DEFAULT_PARAMETERS = _default_parameters.freeze
26
+
27
+ # FSRS-6's default forgetting-curve decay; the default for
28
+ # `current_retrievability`'s decay argument. Equals DEFAULT_PARAMETERS[20].
29
+ FSRS6_DEFAULT_DECAY = 0.1542
30
+
31
+ # Rating encoding, matching Anki / Shirabe's Review::RATINGS.
32
+ RATINGS = { again: 1, hard: 2, good: 3, easy: 4 }.freeze
33
+
34
+ module_function
35
+
36
+ # Train 21 FSRS-6 parameters from review history. `items` is an array of
37
+ # cards, each a chronological array of `{ rating:, delta_t: }` reviews.
38
+ # Returns 21 floats. Releases the GVL — this is CPU-heavy.
39
+ def compute_parameters(items, enable_short_term: true, num_relearning_steps: nil)
40
+ unless num_relearning_steps.nil?
41
+ num_relearning_steps = Integer(num_relearning_steps)
42
+ raise ArgumentError, "num_relearning_steps must be >= 0" if num_relearning_steps.negative?
43
+ end
44
+ _compute_parameters(flatten_items(items), enable_short_term ? true : false, num_relearning_steps)
45
+ end
46
+
47
+ # Retrievability (recall probability) of a memory state after `days_elapsed`
48
+ # days. `state` is `{ stability:, difficulty: }`.
49
+ def current_retrievability(state, days_elapsed, decay = FSRS6_DEFAULT_DECAY)
50
+ stability, difficulty = memory_pair(state)
51
+ _current_retrievability(stability, difficulty, Float(days_elapsed), Float(decay))
52
+ end
53
+
54
+ # Project reviews/day and memorized count over the horizon. Returns a hash of
55
+ # per-day arrays (`:memorized_cnt_per_day`, `:review_cnt_per_day`,
56
+ # `:learn_cnt_per_day`, `:cost_per_day`, `:correct_cnt_per_day`,
57
+ # `:introduced_cnt_per_day`, `:average_desired_retention`). Releases the GVL.
58
+ def simulate(parameters, desired_retention, config: nil, seed: nil, existing_cards: nil)
59
+ result = _simulate(
60
+ params_array(parameters),
61
+ Float(desired_retention),
62
+ stringify_keys(config),
63
+ seed.nil? ? nil : Integer(seed),
64
+ cards_array(existing_cards)
65
+ )
66
+ symbolize_keys(result)
67
+ end
68
+
69
+ # Expected daily workload (seconds) at the given retention. Powers the
70
+ # retention-slider preview.
71
+ def expected_workload(parameters, desired_retention, config: nil)
72
+ _expected_workload(params_array(parameters), Float(desired_retention), stringify_keys(config))
73
+ end
74
+
75
+ # Suggested retention (CMRR) minimizing workload for the memorized target.
76
+ # Releases the GVL.
77
+ def optimal_retention(parameters, config: nil)
78
+ _optimal_retention(params_array(parameters), stringify_keys(config))
79
+ end
80
+
81
+ # Calibrate a SimulatorConfig from Anki-style revlog rows. Each entry is a
82
+ # hash with keys :id, :cid, :usn, :button_chosen, :interval, :last_interval,
83
+ # :ease_factor, :taken_millis, :review_kind (0..4). Returns a config hash
84
+ # suitable for `simulate`/`expected_workload`.
85
+ def extract_simulator_config(revlog_entries, day_cutoff, smooth: true)
86
+ entries = Array(revlog_entries).map { |entry| stringify_keys(entry) }
87
+ config = _extract_simulator_config(entries, Integer(day_cutoff), smooth ? true : false)
88
+ symbolize_keys(config)
89
+ end
90
+
91
+ # ── Shared input coercion (also used by Nagori::FSRS) ──
92
+
93
+ def rating_int(rating)
94
+ value = rating.is_a?(Symbol) ? RATINGS[rating] : rating
95
+ value = Integer(value) if value
96
+ unless value&.between?(1, 4)
97
+ raise ArgumentError, "rating must be 1..4 or #{RATINGS.keys.inspect}, got #{rating.inspect}"
98
+ end
99
+
100
+ value
101
+ end
102
+
103
+ def flatten_reviews(reviews)
104
+ Array(reviews).flat_map do |review|
105
+ rating = rating_int(review.fetch(:rating) { review[:rating] })
106
+ delta_t = Integer(review.fetch(:delta_t) { review[:delta_t] })
107
+ raise ArgumentError, "delta_t must be >= 0, got #{delta_t}" if delta_t.negative?
108
+
109
+ [rating, delta_t]
110
+ end
111
+ end
112
+
113
+ def flatten_items(items)
114
+ Array(items).map { |reviews| flatten_reviews(reviews) }
115
+ end
116
+
117
+ def memory_pair(state)
118
+ raise ArgumentError, "memory state is required" if state.nil?
119
+
120
+ [Float(state.fetch(:stability)), Float(state.fetch(:difficulty))]
121
+ end
122
+
123
+ def memory_pair_or_empty(state)
124
+ state.nil? ? [] : memory_pair(state)
125
+ end
126
+
127
+ def params_array(parameters)
128
+ return [] if parameters.nil?
129
+
130
+ Array(parameters).map { |value| Float(value) }
131
+ end
132
+
133
+ def stringify_keys(hash)
134
+ hash&.to_h { |key, value| [key.to_s, value] }
135
+ end
136
+
137
+ def symbolize_keys(hash)
138
+ hash.to_h { |key, value| [key.to_sym, value] }
139
+ end
140
+
141
+ def cards_array(cards)
142
+ cards.nil? ? nil : Array(cards).map { |card| stringify_keys(card) }
143
+ end
144
+
145
+ # A configured FSRS-6 scheduler. Build once per parameter set and reuse.
146
+ class FSRS
147
+ # nil/empty parameters use DEFAULT_PARAMETERS; 17/19/21-length vectors are
148
+ # accepted (padded internally). Invalid input raises ArgumentError.
149
+ def self.new(parameters = nil)
150
+ _new(Nagori.params_array(parameters))
151
+ end
152
+
153
+ # The 21-float parameter vector (input padded to FSRS-6 length).
154
+ def parameters
155
+ _parameters
156
+ end
157
+
158
+ # Memory states and intervals for each answer button. `memory_state` is nil
159
+ # for a new card or `{ stability:, difficulty: }`. Returns
160
+ # `{ again:, hard:, good:, easy: }`, each `{ stability:, difficulty:,
161
+ # interval: }` with interval in fractional, unrounded days.
162
+ def next_states(memory_state, desired_retention, days_elapsed)
163
+ raw = _next_states(
164
+ Nagori.memory_pair_or_empty(memory_state),
165
+ Float(desired_retention),
166
+ Integer(days_elapsed)
167
+ )
168
+ {
169
+ again: state_at(raw, 0),
170
+ hard: state_at(raw, 3),
171
+ good: state_at(raw, 6),
172
+ easy: state_at(raw, 9)
173
+ }
174
+ end
175
+
176
+ # Memory state after replaying `reviews` (array of `{ rating:, delta_t: }`).
177
+ # `starting_state` seeds a truncated history (e.g. from SM-2). Returns
178
+ # `{ stability:, difficulty: }`.
179
+ def memory_state(reviews, starting_state = nil)
180
+ pair = _memory_state(Nagori.flatten_reviews(reviews), Nagori.memory_pair_or_empty(starting_state))
181
+ { stability: pair[0], difficulty: pair[1] }
182
+ end
183
+
184
+ # Batch form of #memory_state. `items` is an array of review arrays;
185
+ # `starting_states` is a matching array of `{ stability:, difficulty: }` or
186
+ # nil (defaults to all-new). Returns an array of `{ stability:, difficulty: }`.
187
+ def memory_state_batch(items, starting_states = nil)
188
+ flat_items = Nagori.flatten_items(items)
189
+ starts = normalize_starting_states(starting_states, flat_items.length)
190
+ _memory_state_batch(flat_items, starts).each_slice(2).map do |stability, difficulty|
191
+ { stability: stability, difficulty: difficulty }
192
+ end
193
+ end
194
+
195
+ # Approximate a memory state from SM-2 values for cards imported without a
196
+ # full revlog. Returns `{ stability:, difficulty: }`.
197
+ def memory_state_from_sm2(ease_factor, interval, sm2_retention)
198
+ pair = _memory_state_from_sm2(Float(ease_factor), Float(interval), Float(sm2_retention))
199
+ { stability: pair[0], difficulty: pair[1] }
200
+ end
201
+
202
+ # Interval (fractional days) for a memory state at the desired retention.
203
+ # `stability` is nil for a new card (initial stability for `rating` is used;
204
+ # rating is otherwise ignored).
205
+ def next_interval(stability, desired_retention, rating)
206
+ stab = stability.nil? ? [] : [Float(stability)]
207
+ _next_interval(stab, Float(desired_retention), Nagori.rating_int(rating))
208
+ end
209
+
210
+ # Model fit over `items` (array of review arrays). Returns
211
+ # `{ log_loss:, rmse_bins: }`. Releases the GVL.
212
+ def evaluate(items)
213
+ log_loss, rmse_bins = _evaluate(Nagori.flatten_items(items))
214
+ { log_loss: log_loss, rmse_bins: rmse_bins }
215
+ end
216
+
217
+ private
218
+
219
+ def state_at(raw, offset)
220
+ { stability: raw[offset], difficulty: raw[offset + 1], interval: raw[offset + 2] }
221
+ end
222
+
223
+ def normalize_starting_states(starting_states, count)
224
+ return Array.new(count) { [] } if starting_states.nil?
225
+
226
+ unless starting_states.length == count
227
+ raise ArgumentError, "starting_states length #{starting_states.length} != items length #{count}"
228
+ end
229
+
230
+ starting_states.map { |state| Nagori.memory_pair_or_empty(state) }
231
+ end
232
+ end
233
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nagori-fsrs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: x86_64-linux
6
+ authors:
7
+ - davafons
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ description: 'Nagori provides Ruby bindings for fsrs-rs (the `fsrs` crate), a Rust
70
+ implementation of the Free Spaced Repetition Scheduler (FSRS-6): scheduling, memory-state
71
+ replay, parameter optimization, and simulation.'
72
+ email:
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE
78
+ - README.md
79
+ - lib/nagori-fsrs.rb
80
+ - lib/nagori.rb
81
+ - lib/nagori/3.1/nagori.so
82
+ - lib/nagori/3.2/nagori.so
83
+ - lib/nagori/3.3/nagori.so
84
+ - lib/nagori/3.4/nagori.so
85
+ - lib/nagori/4.0/nagori.so
86
+ - lib/nagori/version.rb
87
+ homepage: https://github.com/davafons/nagori-fsrs
88
+ licenses:
89
+ - BSD-3-Clause
90
+ metadata:
91
+ rubygems_mfa_required: 'true'
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '3.1'
101
+ - - "<"
102
+ - !ruby/object:Gem::Version
103
+ version: 4.1.dev
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.5.23
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Ruby bindings for fsrs-rs, the FSRS-6 spaced-repetition scheduler and optimizer
114
+ test_files: []