afterlife 1.8.0 → 1.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f862660ef3c576008d2054a427dcd03fbbbd8a1fe070158507f1eeaa32d288c
4
- data.tar.gz: 9cc64fe488e5c3da7213bd8ca3b61d86539207dfef6a42fc5e043cb5047d3246
3
+ metadata.gz: ded787195d0197794e9ce63aab421fd0b9f4168208bab96b1cdab4e34f22d955
4
+ data.tar.gz: f9305bba34772dbe134eedad518d67ad371e85a1e8b6f28b25785f54f8737e58
5
5
  SHA512:
6
- metadata.gz: 7a81d5767dadaf2ef615236856124f26fb17bdaaf198a5fef2edbfe520ca0aacb8196deab2345605f5d591ecfa35dbffd516db9061c98628b2aecf808986c8df
7
- data.tar.gz: 8666f13424d39b7b5bc045e7b04c0b71de3a5842d3c519963e399fd62fc465ca6242711ad3ed79a1d4ce25e4b5db70065a6ff7fb2774bc1b9954c5851745fe9e
6
+ metadata.gz: decbc189d4fc9e422ab524fdd80adc4234d919542350db532bc04a69a48cdef40220fde5331fb94fa77c1427a59df5d4328f7d454e50d8a8b20d336c239e976b
7
+ data.tar.gz: 394711b68721f95e338b558c51d86581537b6a0ddf7f4dcd31646420cf0a9cebcdfbff60ef2395f4b13acaae1e44856aac5b57b0d690402f46c51b8053a94d81
data/.rubocop.yml CHANGED
@@ -1,10 +1,10 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rspec
3
3
  - rubocop-rake
4
4
 
5
5
  AllCops:
6
6
  NewCops: enable
7
- TargetRubyVersion: 2.6
7
+ TargetRubyVersion: 3.3
8
8
 
9
9
  Lint/ConstantDefinitionInBlock:
10
10
  Enabled: false
@@ -44,3 +44,24 @@ Layout/AccessModifierIndentation:
44
44
 
45
45
  Style/SignalException:
46
46
  EnforcedStyle: semantic
47
+
48
+ Metrics/ClassLength:
49
+ Enabled: false
50
+
51
+ Metrics/CyclomaticComplexity:
52
+ Enabled: false
53
+
54
+ Metrics/MethodLength:
55
+ Enabled: false
56
+
57
+ Metrics/ParameterLists:
58
+ Enabled: false
59
+
60
+ Metrics/PerceivedComplexity:
61
+ Enabled: false
62
+
63
+ RSpec/MultipleExpectations:
64
+ Enabled: false
65
+
66
+ RSpec/MultipleMemoizedHelpers:
67
+ Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.5
1
+ 3.3.5
data/Gemfile CHANGED
@@ -14,3 +14,6 @@ gem 'rspec'
14
14
  gem 'rubocop'
15
15
  gem 'rubocop-rake'
16
16
  gem 'rubocop-rspec'
17
+
18
+ gem 'base64', '~> 0.3.0'
19
+ gem 'benchmark', '~> 0.5.0'
data/afterlife.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = 'Devops utils'
12
12
  spec.description = 'Afterlife helps you setup your development environment and deploy code easily'
13
- spec.required_ruby_version = '>= 2.6'
13
+ spec.required_ruby_version = '>= 3.3'
14
14
 
15
15
  spec.metadata['source_code_uri'] = 'https://bitbucket.org/volabit/afterlife'
16
16
  spec.metadata['changelog_uri'] = 'https://bitbucket.org/volabit/afterlife/src/master/CHANGELOG.md'
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ['lib']
30
30
 
31
31
  spec.add_dependency 'faraday'
32
+ spec.add_dependency 'ostruct'
32
33
  spec.add_dependency 'racc' # rubocop dependency, cannot run `bundle exec rubocop` without it
33
34
  spec.add_dependency 'thor'
34
35
  spec.add_dependency 'zeitwerk'
data/config.ru CHANGED
@@ -3,8 +3,7 @@
3
3
  require 'logger'
4
4
  require 'rack/directory'
5
5
 
6
- logger_file = File.open(Afterlife::Cdn.log_path, 'a')
7
- logger_file.sync = true
6
+ logger = Logger.new(Afterlife::Cdn.log_path)
8
7
 
9
8
  class AssetHeaders
10
9
  def initialize(app)
@@ -19,7 +18,7 @@ class AssetHeaders
19
18
  end
20
19
 
21
20
  use AssetHeaders
22
- use Rack::CommonLogger, Logger.new(logger_file)
21
+ use Rack::CommonLogger, logger
23
22
  run do |env|
24
23
  Rack::Directory.new(Afterlife::Cdn.local_path).call(env)
25
24
  end
@@ -6,9 +6,9 @@ module Afterlife
6
6
  module_function
7
7
 
8
8
  DEFAULT_PRERELEASE_NAME = 'rc'
9
- VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)/.freeze
9
+ VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)/
10
10
 
11
- def calculate_next(part, pre = nil) # rubocop:disable Metrics/MethodLength
11
+ def calculate_next(part, pre = nil)
12
12
  x, y, z = release
13
13
  case part.to_sym
14
14
  when :major
@@ -6,6 +6,7 @@ module Afterlife
6
6
  module Cdn
7
7
  class Cli < Thor
8
8
  include BaseCli
9
+
9
10
  def self.exit_on_failure?
10
11
  true
11
12
  end
data/lib/afterlife/cli.rb CHANGED
@@ -5,6 +5,7 @@ require 'thor'
5
5
  module Afterlife
6
6
  class Cli < Thor
7
7
  include BaseCli
8
+
8
9
  def self.exit_on_failure?
9
10
  true
10
11
  end
@@ -37,10 +37,23 @@ module Afterlife
37
37
  desc: 'Force update status of tasks even if they would not be updated due to status precedence'
38
38
  option :dry_run, type: :boolean, default: false, aliases: '-d',
39
39
  desc: 'Do not actually update the status of the tasks, just print what would be done'
40
- def update_status(target_status, *task_ids)
40
+ option :cherry_pick, type: :boolean, default: false, aliases: '-c',
41
+ desc: 'Include cherry pick status in the output'
42
+ option :verbose, type: :boolean, default: true, aliases: '-v',
43
+ desc: 'Print skipped tasks'
44
+ option :commits, type: :string,
45
+ desc: 'Commit messages used to detect reverted tasks'
46
+ def update_status(target_status, *task_ids) # rubocop:disable Metrics/AbcSize
41
47
  task_ids = $stdin.read.strip.split("\n") if task_ids.empty?
42
48
 
43
- UpdateTaskStatus.call(task_ids, target_status, force: options[:force], dry_run: options[:dry_run])
49
+ UpdateTaskStatus.call(
50
+ task_ids, target_status,
51
+ force: options[:force],
52
+ dry_run: options[:dry_run],
53
+ cherry_pick: options[:cherry_pick],
54
+ verbose: options[:verbose],
55
+ commits: options[:commits],
56
+ )
44
57
  rescue StandardError => e
45
58
  fatal!(e.message)
46
59
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class CommitInputSplitter
6
+ SUBJECT_PATTERN = /\A(?:revert\b|\w+(?:\(.+\))?!?:\s|.+#(?:CU[-_])?[a-z0-9]{8,})/i
7
+
8
+ def self.call(input)
9
+ new(input).call
10
+ end
11
+
12
+ def initialize(input)
13
+ @input = input.to_s.strip
14
+ end
15
+
16
+ def call
17
+ return [] if input.empty?
18
+
19
+ return split_github_input if input.include?('\\n')
20
+
21
+ split_real_newline_input
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :input
27
+
28
+ def split_github_input
29
+ input.lines(chomp: true).filter_map do |line|
30
+ line = line.strip
31
+ line.gsub('\\n', "\n") unless line.empty?
32
+ end
33
+ end
34
+
35
+ def split_real_newline_input
36
+ commits = []
37
+ current = []
38
+
39
+ input.lines(chomp: true).each do |line|
40
+ if subject?(line) && current.any?
41
+ commits << normalize(current)
42
+ current = []
43
+ end
44
+
45
+ current << line
46
+ end
47
+
48
+ commits << normalize(current) if current.any?
49
+ commits.reject(&:empty?)
50
+ end
51
+
52
+ def subject?(line)
53
+ return false if line.empty? || line.start_with?('*', '-', ' ')
54
+ return false if line.match?(/\AThis reverts commit\b/i)
55
+
56
+ line.match?(SUBJECT_PATTERN)
57
+ end
58
+
59
+ def normalize(lines)
60
+ lines.join("\n").strip
61
+ end
62
+ end
63
+ end
64
+ end
@@ -10,7 +10,7 @@ module Afterlife
10
10
  /CU-([a-z0-9]+)/i,
11
11
  ].freeze
12
12
 
13
- def self.call(text, convert_to_link = false) # rubocop:disable Metrics/MethodLength
13
+ def self.call(text, convert_to_link = false)
14
14
  ids = Set.new
15
15
 
16
16
  ID_PATTERNS.each do |pattern|
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class NextTaskStatusDecider
6
+ REVERT_SOURCE_STATUS = 'ready for qa'
7
+ REVERT_TARGET_STATUS = 'needs rework'
8
+
9
+ def self.call(**args)
10
+ new(**args).call
11
+ end
12
+
13
+ def initialize(clickup_task:, target_status:, force: false, reverted: false)
14
+ @clickup_task = clickup_task
15
+ @target_status = target_status
16
+ @force = force
17
+ @reverted = reverted
18
+ @precedence_checker = StatusPrecedenceChecker.new
19
+ end
20
+
21
+ def call
22
+ return if current_status.nil?
23
+
24
+ return reverted_status if reverted
25
+
26
+ skip_update? ? nil : target_status
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :clickup_task, :target_status, :force, :reverted, :precedence_checker
32
+
33
+ def current_status
34
+ @current_status ||= clickup_task&.dig('status', 'status')&.downcase
35
+ end
36
+
37
+ def reverted_status
38
+ REVERT_TARGET_STATUS if current_status == REVERT_SOURCE_STATUS
39
+ end
40
+
41
+ def skip_update?
42
+ !force && precedence_checker.should_skip_update?(current_status, target_status)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -14,7 +14,9 @@ module Afterlife
14
14
  'trunk merged',
15
15
  'ready for qa',
16
16
  'validated qa',
17
+ 'deployed',
17
18
  'completed',
19
+ 'closed',
18
20
  ].freeze
19
21
 
20
22
  def should_skip_update?(current_status, target_status)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class TaskRevertDetector
6
+ def initialize(commits)
7
+ @commits = CommitInputSplitter.call(commits)
8
+ end
9
+
10
+ def reverted?(task_id)
11
+ reverted = false
12
+
13
+ @commits.each do |commit|
14
+ next unless commit.match?(/#?(?:CU[-_])?#{Regexp.escape(task_id)}/i)
15
+
16
+ reverted = revert_count(commit, task_id).odd?
17
+ end
18
+
19
+ reverted
20
+ end
21
+
22
+ private
23
+
24
+ def revert_count(line, task_id)
25
+ before_task_id = line.split(/#?(?:CU[-_])?#{Regexp.escape(task_id)}/i, 2).first
26
+ before_task_id.scan(/\brevert\b/i).count
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,50 +3,101 @@
3
3
  module Afterlife
4
4
  module Clickup
5
5
  class UpdateTaskStatus
6
- attr_reader :target_status, :precedence_checker, :api, :force, :dry_run
6
+ attr_reader :api, :force, :dry_run, :cherry_pick, :verbose, :revert_detector, :slack_output_path
7
7
 
8
- def self.call(task_ids, target_status, force: false, dry_run: false)
9
- new(force: force, dry_run: dry_run).call(task_ids, target_status)
8
+ def self.call(
9
+ task_ids, target_status, force: false, dry_run: false, cherry_pick: false, verbose: true, commits: nil
10
+ )
11
+ new(force:, dry_run:, cherry_pick:, verbose:, commits:).call(task_ids, target_status)
10
12
  end
11
13
 
12
- def initialize(force: false, dry_run: false)
14
+ def initialize(force: false, dry_run: false, cherry_pick: false, verbose: true, commits: nil)
13
15
  @force = force
14
16
  @dry_run = dry_run
17
+ @cherry_pick = cherry_pick
18
+ @verbose = verbose
19
+ @slack_output_path = ENV.fetch('AFTERLIFE_SLACK_OUTPUT', nil)
20
+ write_slack_output('') if slack_output_path
21
+ @revert_detector = TaskRevertDetector.new(commits)
15
22
  token = ENV['CLICKUP_TOKEN'] || fail('CLICKUP_TOKEN required, put `export CLICKUP_TOKEN=...` or the equivalent in your shell') # rubocop:disable Layout/LineLength
16
- @api = Api.new(token: token)
17
- @precedence_checker = StatusPrecedenceChecker.new
23
+ @api = Api.new(token:)
18
24
  end
19
25
 
20
- def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
26
+ def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize
21
27
  if task_ids.empty?
22
- puts 'No tasks to update'
28
+ log 'No tasks to update'
23
29
  return []
24
30
  end
25
31
 
26
- puts 'WARNING: Forcing status updates...' if force
27
- puts 'WARNING: Dry run, no tasks will be updated' if dry_run
28
- puts "Updating #{task_ids.length} tasks to '#{target_status}'"
32
+ log 'WARNING: Forcing status updates...' if force
33
+ log 'WARNING: Dry run, no tasks will be updated' if dry_run
34
+ log "#{dry_run ? 'Would update' : 'Updating'} #{task_ids.length} tasks to '#{target_status}'"
29
35
  moved_task_ids = task_ids.map { |task_id| move_task(task_id, target_status) }.compact
30
- puts "#{dry_run ? 'Would have moved' : 'Moved'} #{moved_task_ids.length} tasks:"
36
+ log "#{dry_run ? 'Would have moved' : 'Moved'} #{moved_task_ids.length} tasks:"
31
37
  moved_task_ids.each do |task|
32
- puts "- #{task[:link]} (from '#{task[:previous_status]}' to '#{task[:new_status]}')"
38
+ cherry_pick_status = " - Cherry Pick: #{task[:is_cherry_pick] ? '' : '❌'}" if cherry_pick
39
+ log "- #{task[:link]} (from '#{task[:previous_status]}' to '#{task[:new_status]}')#{cherry_pick_status}"
33
40
  end
34
41
  moved_task_ids
35
42
  end
36
43
 
37
44
  private
38
45
 
39
- def move_task(task_id, target_status)
40
- current_status = api.get_task(task_id).dig('status', 'status')&.downcase
46
+ def move_task(task_id, target_status) # rubocop:disable Metrics/AbcSize
47
+ task = api.get_task(task_id)
48
+ current_status = task&.dig('status', 'status')&.downcase
49
+ if current_status.nil?
50
+ log "WARNING: Task #{task_id} not found", verbose: true
51
+ return
52
+ end
41
53
 
42
- return puts "WARNING: Task #{task_id} not found" if current_status.nil?
54
+ is_cherry_pick = Array(task['custom_fields']).any? do |field|
55
+ field['name'].match?(/cherry pick/i) && [true, 'true'].include?(field['value'])
56
+ end
43
57
 
44
- should_skip_update = !force && precedence_checker.should_skip_update?(current_status, target_status)
45
58
  task_link = "https://app.clickup.com/t/#{task_id}"
46
- return puts "Skipping task #{task_link} because it's already at '#{current_status}'" if should_skip_update
59
+ next_status = next_status_for(task_id, task, target_status)
60
+ return if next_status.nil?
61
+
62
+ api.update_task(task_id, { status: next_status }) unless dry_run
63
+
64
+ {
65
+ id: task_id,
66
+ link: task_link,
67
+ previous_status: current_status,
68
+ new_status: next_status,
69
+ is_cherry_pick:,
70
+ }
71
+ end
72
+
73
+ def next_status_for(task_id, task, target_status)
74
+ current_status = task.dig('status', 'status')&.downcase
75
+ reverted = revert_detector.reverted?(task_id)
76
+ next_status = NextTaskStatusDecider.call(clickup_task: task, target_status:, force:, reverted:)
77
+
78
+ if reverted && next_status
79
+ log "Moving task https://app.clickup.com/t/#{task_id} to '#{next_status}': reverted in pushed commits"
80
+ elsif !reverted && next_status.nil?
81
+ log(
82
+ "Skipping task https://app.clickup.com/t/#{task_id} because it's already at '#{current_status}'",
83
+ verbose: true,
84
+ )
85
+ end
86
+
87
+ next_status
88
+ end
89
+
90
+ def log(message, verbose: false)
91
+ return if verbose && !@verbose
92
+
93
+ puts message
94
+ write_slack_output("#{message}\n", mode: 'a') if !verbose && slack_output_path
95
+ end
47
96
 
48
- api.update_task(task_id, { status: target_status }) unless dry_run
49
- { id: task_id, link: task_link, previous_status: current_status, new_status: target_status }
97
+ def write_slack_output(message, mode: 'w')
98
+ File.write(slack_output_path, message, mode:)
99
+ rescue SystemCallError
100
+ nil
50
101
  end
51
102
  end
52
103
  end
@@ -4,6 +4,7 @@ module Afterlife
4
4
  module Config
5
5
  class Cli < Thor
6
6
  include BaseCli
7
+
7
8
  def self.exit_on_failure?
8
9
  true
9
10
  end
@@ -20,7 +20,7 @@ module Afterlife
20
20
  config.key?(sym) || super
21
21
  end
22
22
 
23
- def method_missing(sym, *args, &block)
23
+ def method_missing(sym, *args, &)
24
24
  config.key?(sym) ? config[sym] : super
25
25
  end
26
26
 
@@ -4,6 +4,7 @@ module Afterlife
4
4
  module Deploy
5
5
  class Cli < Thor
6
6
  include BaseCli
7
+
7
8
  def self.exit_on_failure?
8
9
  true
9
10
  end
@@ -28,8 +28,8 @@ module Afterlife
28
28
  def send_request(repository, branch)
29
29
  uri = URI(notification_url)
30
30
  body = {
31
- branch: branch,
32
- repository: repository,
31
+ branch:,
32
+ repository:,
33
33
  }
34
34
  headers = { 'Content-Type': 'application/json' }
35
35
  Net::HTTP.post(uri, body.to_json, headers)
@@ -4,6 +4,7 @@ module Afterlife
4
4
  module Propagate
5
5
  class Cli < Thor
6
6
  include BaseCli
7
+
7
8
  def self.exit_on_failure?
8
9
  true
9
10
  end
@@ -116,7 +116,7 @@ module Afterlife
116
116
 
117
117
  def generate_tag_name
118
118
  now = Time.now
119
- year = now.year.to_s[2..3] # Last two digits of year
119
+ year = now.year.to_s[2..3] # Last two digits of year
120
120
  month = format('%02d', now.month)
121
121
  day = format('%02d', now.day)
122
122
  "#{year}#{month}#{day}"
@@ -3,7 +3,7 @@
3
3
  module Afterlife
4
4
  module Release
5
5
  class ChangeVersion
6
- RUBY_REGEXP = /VERSION\s*=\s*['"]#{Bump::Semver::VERSION_REGEX}['"]/.freeze
6
+ RUBY_REGEXP = /VERSION\s*=\s*['"]#{Bump::Semver::VERSION_REGEX}['"]/
7
7
  REGEXPS = {
8
8
  ruby: RUBY_REGEXP,
9
9
  gem: RUBY_REGEXP,
@@ -26,7 +26,7 @@ module Afterlife
26
26
  assert_git_dependency
27
27
  fatal! "Part '#{part}' is not allowed" unless CreateHelper::PARTS.include?(part.to_sym)
28
28
 
29
- CreateHelper.call(self, options.merge(part: part))
29
+ CreateHelper.call(self, options.merge(part:))
30
30
  end
31
31
 
32
32
  desc 'hotfix', 'create a hotfix branch'
@@ -19,7 +19,7 @@ module Afterlife
19
19
  end
20
20
 
21
21
  def call
22
- confirm_creation unless cli.options[:yes]
22
+ creation_prompt unless cli.options[:yes]
23
23
  make_assertions unless cli.options[:force]
24
24
  create_branch
25
25
  ChangeVersion.call(new_version)
@@ -49,10 +49,11 @@ module Afterlife
49
49
  cli.fatal! 'Local and remote are not on the same commit'
50
50
  end
51
51
 
52
- def confirm_creation
52
+ def creation_prompt
53
53
  cli.sure? 'You are about to create the branch ' \
54
54
  "#{cli.set_color(branch_name, :bold)} " \
55
55
  "from #{cli.set_color(from, :bold)}"
56
+ nil
56
57
  end
57
58
 
58
59
  def create_branch
@@ -4,20 +4,21 @@ module Afterlife
4
4
  module Release
5
5
  class PreHelper < Helper
6
6
  def call
7
- confirm_creation
7
+ creation_prompt
8
8
  check_tag_does_not_exist
9
9
  commit
10
10
  create_tag
11
11
  push_tag if cli.options[:push]
12
12
  end
13
13
 
14
- def confirm_creation
14
+ def creation_prompt
15
15
  return if cli.options[:yes]
16
16
 
17
- cli.sure? 'You are about to create ' \
18
- "#{cli.options[:push] ? '(and push) ' : ''}" \
17
+ action = cli.options[:push] ? '(and push) ' : ''
18
+ cli.sure? "You are about to create #{action}" \
19
19
  "the tag #{cli.set_color(tag, :bold)} " \
20
20
  'in the current commit'
21
+ nil
21
22
  end
22
23
 
23
24
  def check_tag_does_not_exist
@@ -5,24 +5,24 @@ module Afterlife
5
5
  # Reads configuration from the repo,
6
6
  # either from YML or from the repo instance methods
7
7
  class Config
8
- Error = Class.new StandardError
8
+ class Error < StandardError; end
9
9
 
10
10
  attr_reader :path
11
11
 
12
- def self.read(path, &block)
13
- new(path).read(&block)
12
+ def self.read(path, &)
13
+ new(path).read(&)
14
14
  end
15
15
 
16
16
  def initialize(path)
17
17
  @path = path
18
18
  end
19
19
 
20
- def read(&block) # rubocop:disable Metrics/MethodLength
20
+ def read(&)
21
21
  case from_yml
22
22
  when Hash
23
23
  yield(JSON.generate(from_yml))
24
24
  when Array
25
- from_yml.each(&block)
25
+ from_yml.each(&)
26
26
  when String, Symbol
27
27
  yield(from_yml)
28
28
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Afterlife
4
- VERSION = '1.8.0'
4
+ VERSION = '1.9.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: afterlife
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Genaro Madrid
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-10 00:00:00.000000000 Z
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ostruct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: racc
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -97,9 +111,12 @@ files:
97
111
  - lib/afterlife/clickup.rb
98
112
  - lib/afterlife/clickup/api.rb
99
113
  - lib/afterlife/clickup/cli.rb
114
+ - lib/afterlife/clickup/commit_input_splitter.rb
100
115
  - lib/afterlife/clickup/get_ids_from_text.rb
101
116
  - lib/afterlife/clickup/get_range_commits.rb
117
+ - lib/afterlife/clickup/next_task_status_decider.rb
102
118
  - lib/afterlife/clickup/status_precedence_checker.rb
119
+ - lib/afterlife/clickup/task_revert_detector.rb
103
120
  - lib/afterlife/clickup/update_task_status.rb
104
121
  - lib/afterlife/config.rb
105
122
  - lib/afterlife/config/cli.rb
@@ -144,14 +161,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
161
  requirements:
145
162
  - - ">="
146
163
  - !ruby/object:Gem::Version
147
- version: '2.6'
164
+ version: '3.3'
148
165
  required_rubygems_version: !ruby/object:Gem::Requirement
149
166
  requirements:
150
167
  - - ">="
151
168
  - !ruby/object:Gem::Version
152
169
  version: '0'
153
170
  requirements: []
154
- rubygems_version: 3.2.33
171
+ rubygems_version: 3.5.16
155
172
  signing_key:
156
173
  specification_version: 4
157
174
  summary: Devops utils