sof-cycle 0.1.6 → 0.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33cc5f466f7776bd9476664153cf9ae0e3f898132b348cffa0117a911ad7dda6
4
- data.tar.gz: a30b3ea58dce3a953531deffba10f9199af3b283fdf70f99d7af55988aef428e
3
+ metadata.gz: bcb187376738c3b567bf2bc9fc68bba6f1cb7e766302a897ab272db848b1b081
4
+ data.tar.gz: d841689c22f68f7f273d1791feb7753826c3b5c2235deb467fad88ee163ec18f
5
5
  SHA512:
6
- metadata.gz: ce71b8b026ee7e78c6d6568dea2a88eb2d5faee74cbc4cc4e7c6f78bf7e328b46ba7017e71acf75f7b8613204e6e7a56b9592987d559c5883c1901ead1119718
7
- data.tar.gz: 306c295e768f6616c80ebe3ee8adbfb734d64b01b2b4fd5d52a45ac9e913752b660bbc7a77129adfd66fb517adb6c0217ec11f4c62518c32620c788a3803c91b
6
+ metadata.gz: f193e2a83e149315956c99df5efebabfd05c9ade453bb4e4763db1a39870c62c63bae5e6c82379bddd1f07a85a1a6f898fb90d53a753f0936f146efdf6e5f786
7
+ data.tar.gz: 7000bd01fd3c3302756b757b406f36ec75db9125a3acf597e5c6fb54ab811356e076b4d45087098db94ffb4d4f0151b33d8bd4ce3c1ebe4e4844ae20029aec66
@@ -0,0 +1,17 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(find:*)",
5
+ "Bash(ls:*)",
6
+ "Bash(mkdir:*)",
7
+ "Bash(touch:*)",
8
+ "Bash(rm:*)",
9
+ "Bash(bundle exec rspec:*)",
10
+ "Bash(bundle exec rake:*)",
11
+ "Bash(bundle exec standardrb:*)",
12
+ "Bash(git checkout:*)",
13
+ "Bash(bundle exec ruby:*)"
14
+ ],
15
+ "deny": []
16
+ }
17
+ }
data/.simplecov CHANGED
@@ -1,4 +1,10 @@
1
1
  require "simplecov"
2
+ require "simplecov_json_formatter"
3
+
2
4
  SimpleCov.start do
5
+ formatter SimpleCov::Formatter::MultiFormatter.new([
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ SimpleCov::Formatter::JSONFormatter
8
+ ])
3
9
  add_filter "/spec/"
4
10
  end
data/CHANGELOG.md CHANGED
@@ -5,14 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.6] - 2024-09-04
8
+ ## [0.1.8] - 2025-09-04
9
9
 
10
- ### Added
10
+ ### Changed
11
+
12
+ - Simplified `handles?` logic to do string comparison instead of symbol comparison.
11
13
 
12
- - `Cycle.extend_period(count)` to get a new cycle with the modified period count
14
+ ## [0.1.7] - 2025-06-09
15
+
16
+ ### Added
13
17
 
14
- ## [0.1.5] - 2024-09-02
18
+ - `Cycle#considered_dates` to get the subset of `#covered_dates` that are
19
+ considered for the cycle's calculations.
15
20
 
16
21
  ### Fixed
17
22
 
18
- - `Cycle#last_completed` works for dormant cycles too
23
+ - `Cycles::Lookback.volume_to_delay_expiration` now computes correctly when the
24
+ `#considered_dates` is smaller than the `#covered_dates` of the cycle.
data/CLAUDE.md ADDED
@@ -0,0 +1,34 @@
1
+ # CLAUDE.md
2
+
3
+ ## Agent OS Documentation
4
+
5
+ ### Product Context
6
+ - **Mission & Vision:** @.agent-os/product/mission.md
7
+ - **Technical Architecture:** @.agent-os/product/tech-stack.md
8
+ - **Development Roadmap:** @.agent-os/product/roadmap.md
9
+ - **Decision History:** @.agent-os/product/decisions.md
10
+
11
+ ### Development Standards
12
+ - **Code Style:** @~/.agent-os/standards/code-style.md
13
+ - **Best Practices:** @~/.agent-os/standards/best-practices.md
14
+
15
+ ### Project Management
16
+ - **Active Specs:** @.agent-os/specs/
17
+ - **Spec Planning:** Use `@~/.agent-os/instructions/create-spec.md`
18
+ - **Tasks Execution:** Use `@~/.agent-os/instructions/execute-tasks.md`
19
+
20
+ ## Workflow Instructions
21
+
22
+ When asked to work on this codebase:
23
+
24
+ 1. **First**, check @.agent-os/product/roadmap.md for current priorities
25
+ 2. **Then**, follow the appropriate instruction file:
26
+ - For new features: @~/.agent-os/instructions/create-spec.md
27
+ - For tasks execution: @~/.agent-os/instructions/execute-tasks.md
28
+ 3. **Always**, adhere to the standards in the files listed above
29
+
30
+ ## Important Notes
31
+
32
+ - Product-specific files in `.agent-os/product/` override any global standards
33
+ - User's specific instructions override (or amend) instructions found in `.agent-os/specs/...`
34
+ - Always adhere to established patterns, code style, and best practices documented above.
@@ -0,0 +1 @@
1
+ 4a0a79b2c4566319f3a28a400b5c5e471a4d3e6acf979f02b38546cfab34c11d4dd1b1b5118eab0b5cbcab77ed54fb896dc81f4fa58afe41ae437ed79f44f235
@@ -0,0 +1 @@
1
+ 10f35795f9559afbf1b87c26b76c5458fbdca373705539bfb55e3702c372b9198dba2cf1986489c5a95b55359dcdcfe39e27b10564e6570a94552ab95f31de7f
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SOF
4
4
  class Cycle
5
- VERSION = "0.1.6"
5
+ VERSION = "0.1.8"
6
6
  end
7
7
  end
data/lib/sof/cycle.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
3
4
  require_relative "parser"
4
5
 
5
6
  module SOF
6
7
  class Cycle
7
- extend Forwardable
8
+ extend ::Forwardable
8
9
  class InvalidInput < StandardError; end
9
10
 
10
11
  class InvalidPeriod < InvalidInput; end
@@ -99,12 +100,26 @@ module SOF
99
100
  end || raise(InvalidKind, "':#{sym}' is not a valid kind of Cycle")
100
101
  end
101
102
 
102
- def cycle_handlers = @cycle_handlers ||= Set.new
103
-
104
- def inherited(klass) = cycle_handlers << klass
105
-
106
- def handles?(sym)
107
- sym && kind == sym.to_sym
103
+ # Return a legend explaining all notation components
104
+ #
105
+ # @return [Hash] hash with notation components organized by category
106
+ def legend
107
+ {
108
+ "quantity" => {
109
+ "V" => {
110
+ description: "Volume - the number of times something should occur",
111
+ examples: ["V1L1D - once in the prior 1 day", "V3L3D - three times in the prior 3 days", "V10L10D - ten times in the prior 10 days"]
112
+ }
113
+ },
114
+ "kind" => build_kind_legend,
115
+ "period" => build_period_legend,
116
+ "date" => {
117
+ "F" => {
118
+ description: "From - specifies the anchor date for Within cycles",
119
+ examples: ["F2024-01-01 - from January 1, 2024", "F2024-12-31 - from December 31, 2024"]
120
+ }
121
+ }
122
+ }
108
123
  end
109
124
 
110
125
  @volume_only = false
@@ -127,6 +142,65 @@ module SOF
127
142
  #{valid_periods.join(", ")}
128
143
  ERR
129
144
  end
145
+
146
+ def handles?(sym)
147
+ kind.to_s == sym.to_s
148
+ end
149
+
150
+ def cycle_handlers
151
+ @cycle_handlers ||= Set.new
152
+ end
153
+
154
+ def inherited(klass)
155
+ cycle_handlers << klass
156
+ end
157
+
158
+ private
159
+
160
+ def build_kind_legend
161
+ legend = {}
162
+ cycle_handlers.each do |handler|
163
+ # Skip volume_only since it doesn't have a notation_id
164
+ next if handler.instance_variable_get(:@volume_only)
165
+
166
+ notation_id = handler.instance_variable_get(:@notation_id)
167
+ next unless notation_id
168
+
169
+ legend[notation_id] = {
170
+ description: handler.description,
171
+ examples: handler.examples
172
+ }
173
+ end
174
+ legend
175
+ end
176
+
177
+ def build_period_legend
178
+ legend = {}
179
+ # Use known period codes since DatePeriod is private
180
+ period_mappings = {
181
+ "D" => "day",
182
+ "W" => "week",
183
+ "M" => "month",
184
+ "Q" => "quarter",
185
+ "Y" => "year"
186
+ }
187
+
188
+ period_mappings.each do |code, period_name|
189
+ legend[code] = {
190
+ description: "#{period_name.capitalize} - period notation",
191
+ examples: period_examples_for(code, period_name)
192
+ }
193
+ end
194
+ legend
195
+ end
196
+
197
+ def period_examples_for(code, period_name)
198
+ base_example = (code == "D") ? "3#{code} - 3 #{period_name}s" : "2#{code} - 2 #{period_name}s"
199
+ lookback_example = "L#{(code == "D") ? "7" : "4"}#{code} - in the prior #{(code == "D") ? "7" : "4"} #{period_name}s"
200
+ calendar_example = "C1#{code} - this calendar #{period_name}"
201
+
202
+ [base_example, lookback_example, calendar_example]
203
+ end
130
204
  end
131
205
 
132
206
  def initialize(notation, parser: Parser.new(notation))
@@ -175,6 +249,10 @@ module SOF
175
249
  covered_dates(completion_dates, anchor:).size >= volume
176
250
  end
177
251
 
252
+ def considered_dates(completion_dates, anchor: Date.current)
253
+ covered_dates(completion_dates, anchor:).max_by(volume) { it }
254
+ end
255
+
178
256
  def covered_dates(dates, anchor: Date.current)
179
257
  dates.select do |date|
180
258
  cover?(date, anchor:)
@@ -10,6 +10,14 @@ module SOF
10
10
 
11
11
  class << self
12
12
  def frame_of_reference = "total"
13
+
14
+ def description
15
+ "Calendar - occurrences within the current calendar period"
16
+ end
17
+
18
+ def examples
19
+ ["V2C1M - twice this calendar month", "V4C1Y - 4 times this calendar year"]
20
+ end
13
21
  end
14
22
 
15
23
  def self.recurring? = true
@@ -22,7 +30,7 @@ module SOF
22
30
  # @return [Date, nil] the date on which the cycle will expire given the
23
31
  # provided completion dates. Returns nil if the cycle is already unsatisfied.
24
32
  def expiration_of(completion_dates)
25
- anchor = completion_dates.max_by(volume) { _1 }.min
33
+ anchor = completion_dates.max_by(volume) { it }.min
26
34
  return unless satisfied_by?(completion_dates, anchor:)
27
35
 
28
36
  window_end(anchor) + duration
@@ -18,6 +18,14 @@ module SOF
18
18
 
19
19
  def self.recurring? = true
20
20
 
21
+ def self.description
22
+ "End of - occurrences by the end of a time period"
23
+ end
24
+
25
+ def self.examples
26
+ ["V1E1M - once by the end of next month", "V2E2Q - twice by the end of 2 quarters"]
27
+ end
28
+
21
29
  def to_s
22
30
  return dormant_to_s if dormant?
23
31
 
@@ -10,18 +10,30 @@ module SOF
10
10
 
11
11
  def self.recurring? = true
12
12
 
13
+ def self.description
14
+ "Lookback - occurrences within a prior time period counting backwards from today"
15
+ end
16
+
17
+ def self.examples
18
+ ["V3L3D - 3 times in the prior 3 days", "V1L2W - once in the prior 2 weeks"]
19
+ end
20
+
13
21
  def to_s = "#{volume}x in the prior #{period_count} #{humanized_period}"
14
22
 
15
23
  def volume_to_delay_expiration(completion_dates, anchor:)
16
- oldest_relevant_completion = completion_dates.min
17
- [completion_dates.count(oldest_relevant_completion), volume].min
24
+ relevant_dates = considered_dates(completion_dates, anchor:)
25
+ return unless satisfied_by?(relevant_dates, anchor:)
26
+
27
+ # To move the expiration date, we need to displace each occurance of the
28
+ # oldest date within #considered_dates.
29
+ relevant_dates.count(relevant_dates.min)
18
30
  end
19
31
 
20
32
  # "Absent further completions, you go red on this date"
21
33
  # @return [Date, nil] the date on which the cycle will expire given the
22
34
  # provided completion dates. Returns nil if the cycle is already unsatisfied.
23
35
  def expiration_of(completion_dates)
24
- anchor = completion_dates.max_by(volume) { _1 }.min
36
+ anchor = completion_dates.max_by(volume) { it }.min
25
37
  return unless satisfied_by?(completion_dates, anchor:)
26
38
 
27
39
  window_end anchor
@@ -9,7 +9,7 @@ module SOF
9
9
  @valid_periods = []
10
10
 
11
11
  class << self
12
- def handles?(sym) = sym.nil? || super
12
+ def handles?(sym) = sym.nil? || sym.to_s == "volume_only"
13
13
 
14
14
  def validate_period(period)
15
15
  raise InvalidPeriod, <<~ERR.squish unless period.nil?
@@ -10,6 +10,14 @@ module SOF
10
10
 
11
11
  def self.recurring? = false
12
12
 
13
+ def self.description
14
+ "Within - occurrences within a time period from a specific date"
15
+ end
16
+
17
+ def self.examples
18
+ ["V2W3DF2024-01-01 - twice within 3 days from Jan 1, 2024"]
19
+ end
20
+
13
21
  def to_s = "#{volume}x within #{date_range}"
14
22
 
15
23
  def extend_period(count)
@@ -23,7 +31,7 @@ module SOF
23
31
  def date_range
24
32
  return humanized_span unless active?
25
33
 
26
- [start_date, final_date].map { _1.to_fs(:american) }.join(" - ")
34
+ [start_date, final_date].map { it.to_fs(:american) }.join(" - ")
27
35
  end
28
36
 
29
37
  def final_date(_ = nil) = time_span.end_date(start_date)
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sof-cycle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: forwardable
@@ -38,21 +37,24 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '6.0'
41
- description:
42
40
  email:
43
41
  - jim@saturnflyer.com
44
42
  executables: []
45
43
  extensions: []
46
44
  extra_rdoc_files: []
47
45
  files:
46
+ - ".claude/settings.local.json"
48
47
  - ".rspec"
49
48
  - ".simplecov"
50
49
  - CHANGELOG.md
50
+ - CLAUDE.md
51
51
  - README.md
52
52
  - Rakefile
53
53
  - checksums/sof-cycle-0.1.0.gem.sha512
54
54
  - checksums/sof-cycle-0.1.1.gem.sha512
55
55
  - checksums/sof-cycle-0.1.2.gem.sha512
56
+ - checksums/sof-cycle-0.1.6.gem.sha512
57
+ - checksums/sof-cycle-0.1.7.gem.sha512
56
58
  - lib/sof-cycle.rb
57
59
  - lib/sof/cycle.rb
58
60
  - lib/sof/cycle/version.rb
@@ -69,7 +71,6 @@ licenses: []
69
71
  metadata:
70
72
  homepage_uri: https://github.com/SOFware/sof-cycle
71
73
  changelog_uri: https://github.com/SOFware/sof-cycle/blob/main/CHANGELOG.md
72
- post_install_message:
73
74
  rdoc_options: []
74
75
  require_paths:
75
76
  - lib
@@ -84,8 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
84
85
  - !ruby/object:Gem::Version
85
86
  version: '0'
86
87
  requirements: []
87
- rubygems_version: 3.5.11
88
- signing_key:
88
+ rubygems_version: 3.6.7
89
89
  specification_version: 4
90
90
  summary: Parse and interact with SOF cycle notation.
91
91
  test_files: []