ace-task 0.37.4 → 0.37.5

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: e59cae852198045f684e486ebb3ac8fc07cf96b32098323de7fc76644ff11cb5
4
- data.tar.gz: 97d7f2199699256db60426be3e68ee503f3c3eea935a99cb597d6de38a41e52d
3
+ metadata.gz: 722bb3a4374541de1d0b486d404772f5a46d02546f05c46e8dcc4595eeb2a5ef
4
+ data.tar.gz: 98d289a6740f8ecdbb65b31e96af5cf5b1b851c97214eb449e46ede6baf20939
5
5
  SHA512:
6
- metadata.gz: 3364a66e7e03dd6e8e3b5fc9aa168266ee1899b8922359f1f240428082bc4e83913d01a5d0e53cb5a1220afe42afbc7a69afd0ffadd907b56fdf55900186a13b
7
- data.tar.gz: 1fe73e05c9660719fa0494e437abaf640f774cf96862c59a84009eb6fdc761d5568d3fa54c469e810bcbaddc90524339f1de67cf6fccea4ec5d1034fd4db5b08
6
+ metadata.gz: 740ebb0c5b0e3c2ae9068717f8b8c6e2aee0896f9160bf1df7889975cdede6aa10ace2c392ae9cbede915e78ccf151e6cf4a78b0c22826e9f76aec75ef324e79
7
+ data.tar.gz: f1b69097da5391ec1554cf9debc11867f587b29321b74cc5cc9dfdf8e87899387a9dc08fb5adeec8a87ee135e5b24405d16542823e35290d9f8a6d8998855970
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.37.5] - 2026-09-26
11
+
12
+ ### Added
13
+ - Best-effort GitHub sync: updates run without `gh`; offline updates flag tasks with `github_sync_pending` frontmatter, and `ace-task github-sync --pending` replays the missed issue syncs once gh is installed and authenticated.
14
+ - gh availability gate (`IssueSync.available?` via ace-git-github): ownership validation and post-update issue sync run only when gh is installed and authenticated, restoring full checks when it is.
15
+
16
+ ### Fixed
17
+ - Require the declared `ace-git-github` dependency so updates and archive moves of tasks with linked GitHub issues no longer fail with "GitHub issue sync primitives are unavailable".
10
18
 
11
19
  ## [0.37.4] - 2026-09-02
12
20
 
@@ -16,25 +16,27 @@ module Ace
16
16
 
17
17
  example [
18
18
  "q7w # Sync one task",
19
- "--all # Sync all linked tasks"
19
+ "--all # Sync all linked tasks",
20
+ "--pending # Replay tasks flagged with github_sync_pending (offline sync)"
20
21
  ]
21
22
 
22
23
  argument :ref, required: false, desc: "Task reference (full ID, short ref, or suffix)"
23
24
  option :all, type: :boolean, aliases: %w[-a], desc: "Sync all linked tasks"
25
+ option :pending, type: :boolean, desc: "Sync only tasks flagged with github_sync_pending"
24
26
 
25
27
  option :quiet, type: :boolean, aliases: %w[-q], desc: "Suppress non-essential output"
26
28
  option :verbose, type: :boolean, aliases: %w[-v], desc: "Show verbose output"
27
29
  option :debug, type: :boolean, aliases: %w[-d], desc: "Show debug output"
28
30
 
29
31
  def call(ref: nil, **options)
30
- all = options[:all]
32
+ all = options[:all] || options[:pending]
31
33
 
32
34
  if !all && (ref.nil? || ref.strip.empty?)
33
35
  raise Ace::Support::Cli::Error.new("Provide a task reference or use --all")
34
36
  end
35
37
 
36
38
  manager = Ace::Task::Organisms::TaskManager.new
37
- result = manager.github_sync(ref: ref, all: all)
39
+ result = manager.github_sync(ref: ref, all: options[:all], pending: options[:pending])
38
40
  raise Ace::Support::Cli::Error.new("Task '#{ref}' not found") if result.nil?
39
41
  print_failures(result[:failures])
40
42
 
@@ -42,8 +44,14 @@ module Ace
42
44
  raise Ace::Support::Cli::Error.new(
43
45
  "GitHub sync incomplete: synced #{result[:synced]}, failed #{result[:failed]}, skipped #{result[:skipped]}"
44
46
  )
47
+ elsif options[:pending] && result[:synced].to_i.zero? && result[:pending].to_i.zero?
48
+ puts "No pending GitHub syncs"
45
49
  elsif all
46
50
  puts "GitHub sync complete: synced #{result[:synced]} linked task(s), skipped #{result[:skipped]} task(s)"
51
+ pending = result[:pending].to_i
52
+ warn "gh unavailable: #{pending} task(s) remain flagged for github-sync --pending" if pending.positive?
53
+ elsif result[:pending].to_i.positive?
54
+ puts "GitHub sync skipped (gh unavailable); flagged for github-sync --pending"
47
55
  elsif result[:synced].positive?
48
56
  puts "GitHub sync complete: #{result[:task_id]}"
49
57
  else
@@ -4,19 +4,29 @@ module Ace
4
4
  module Task
5
5
  module Molecules
6
6
  # Bridges ace-task lifecycle events to reusable ace-git issue sync primitives.
7
+ # GitHub sync is best-effort: when gh is unavailable, operations no-op and
8
+ # callers record the pending sync on the task instead.
7
9
  class GithubIssueSyncAdapter
8
10
  UNAVAILABLE_MESSAGE = "GitHub issue sync primitives are unavailable. " \
9
- "Complete dependency task 8r4.t.ilo.2 or update ace-git."
11
+ "Update ace-git-github to a version providing them."
10
12
 
11
13
  CANDIDATE_INTEGRATIONS = [
12
- ["Ace::Git::Molecules::GithubIssueSync", :validate_link!],
13
- ["Ace::Git::Molecules::IssueSync", :validate_link!],
14
- ["Ace::Git::Molecules::GithubIssueSync", :sync_task],
15
- ["Ace::Git::Molecules::IssueSync", :sync_task]
14
+ ["Ace::Git::Github::IssueSync", :validate_link!],
15
+ ["Ace::Git::Github::IssueSync", :sync_task]
16
16
  ].freeze
17
17
 
18
+ # True when GitHub sync should run: the integration resolves and reports
19
+ # gh as installed and authenticated.
20
+ def available?
21
+ receiver, = resolve_integration(:sync_task)
22
+ return false unless receiver
23
+
24
+ receiver.respond_to?(:available?) ? receiver.available? : false
25
+ end
26
+
18
27
  def validate_link!(issue_id:, previous_task: nil)
19
28
  return unless issue_id.to_i.positive?
29
+ return unless available?
20
30
 
21
31
  receiver, method_name = resolve_integration(:validate_link!)
22
32
  raise UNAVAILABLE_MESSAGE unless receiver && method_name
@@ -33,7 +43,8 @@ module Ace
33
43
  current_issue_ids = [task.metadata["github_issue"]].compact.map(&:to_i).uniq
34
44
  previous_issue_ids = [previous_task&.metadata&.[]("github_issue")].compact.map(&:to_i).uniq
35
45
  issue_ids = (current_issue_ids + previous_issue_ids).uniq
36
- return {synced: 0, issues: []} if issue_ids.empty?
46
+ return {synced: 0, issues: []} if issue_ids.empty? || !available?
47
+
37
48
 
38
49
  receiver, method_name = resolve_integration(:sync_task)
39
50
  raise UNAVAILABLE_MESSAGE unless receiver && method_name
@@ -136,15 +136,22 @@ module Ace
136
136
 
137
137
  def validate_github_fields(frontmatter, file_path, issues)
138
138
  linked = frontmatter["github_issue"]
139
- return if linked.nil?
140
-
141
- unless linked.is_a?(Integer) && linked.positive?
139
+ unless linked.nil? || (linked.is_a?(Integer) && linked.positive?)
142
140
  issues << {
143
141
  type: :error,
144
142
  message: "Invalid GitHub issue ID '#{linked}' in github_issue (expected positive integer)",
145
143
  location: file_path
146
144
  }
147
145
  end
146
+
147
+ pending = frontmatter["github_sync_pending"]
148
+ return if pending.nil? || pending == true || pending == false
149
+
150
+ issues << {
151
+ type: :error,
152
+ message: "Invalid github_sync_pending value (expected boolean)",
153
+ location: file_path
154
+ }
148
155
  end
149
156
  end
150
157
  end
@@ -227,11 +227,12 @@ module Ace
227
227
  created_subtask
228
228
  end
229
229
 
230
- def github_sync(ref: nil, all: false)
231
- raise ArgumentError, "Provide --all or a task reference" if !all && (ref.nil? || ref.strip.empty?)
230
+ def github_sync(ref: nil, all: false, pending: false)
231
+ raise ArgumentError, "Provide --all or a task reference" if !all && !pending && (ref.nil? || ref.strip.empty?)
232
232
 
233
- if all
233
+ if all || pending
234
234
  tasks = list(in_folder: "all")
235
+ tasks = tasks.select { |t| t.metadata["github_sync_pending"] } if pending
235
236
  linked_tasks = tasks.select { |t| linked_issue_id(t) }
236
237
  results = linked_tasks.map { |task| sync_linked_issues_for(task, reason: "manual-sync") }
237
238
  return summarize_manual_sync_results(results, skipped: tasks.length - linked_tasks.length)
@@ -482,13 +483,43 @@ module Ace
482
483
  return sync_result_for(task: task, issues: issue_ids, success: true, reason: reason) if issue_ids.empty?
483
484
 
484
485
  adapter = Molecules::GithubIssueSyncAdapter.new
486
+ unless adapter.available?
487
+ mark_github_sync_pending(task)
488
+ @last_update_note = "GitHub sync skipped (gh unavailable); flagged for 'ace-task github-sync --pending'"
489
+ return sync_result_for(task: task, issues: issue_ids, success: true, reason: reason).merge(offline: true)
490
+ end
491
+
485
492
  adapter.sync_task(task: task, reason: reason, previous_task: previous_task)
493
+ clear_github_sync_pending(task)
486
494
  sync_result_for(task: task, issues: issue_ids, success: true, reason: reason)
487
495
  rescue StandardError => e
488
- @last_update_note = "GitHub sync warning for task #{task&.id}: #{e.message}"
496
+ mark_github_sync_pending(task)
497
+ @last_update_note = "GitHub sync warning for task #{task&.id}: #{e.message}; flagged for 'ace-task github-sync --pending'"
489
498
  sync_result_for(task: task, issues: issue_ids, success: false, reason: reason, error: e.message)
490
499
  end
491
500
 
501
+ # Flag the task so the missed GitHub sync survives offline work and can be
502
+ # replayed with 'ace-task github-sync --pending'.
503
+ def mark_github_sync_pending(task)
504
+ return unless task&.file_path && File.exist?(task.file_path)
505
+
506
+ Ace::Support::Items::Molecules::FieldUpdater.update(
507
+ task.file_path, set: {"github_sync_pending" => true}
508
+ )
509
+ rescue StandardError
510
+ nil
511
+ end
512
+
513
+ def clear_github_sync_pending(task)
514
+ return unless task&.file_path && File.exist?(task.file_path)
515
+
516
+ Ace::Support::Items::Molecules::FieldUpdater.update(
517
+ task.file_path, set: {"github_sync_pending" => nil}
518
+ )
519
+ rescue StandardError
520
+ nil
521
+ end
522
+
492
523
  def sync_result_for(task:, issues:, success:, reason:, error: nil)
493
524
  {
494
525
  task_id: task&.id,
@@ -508,9 +539,12 @@ module Ace
508
539
  }
509
540
  end
510
541
 
542
+ pending = results.count { |result| result[:offline] }
543
+
511
544
  {
512
- synced: results.length - failures.length,
545
+ synced: results.length - failures.length - pending,
513
546
  failed: failures.length,
547
+ pending: pending,
514
548
  skipped: skipped,
515
549
  failures: failures
516
550
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Task
5
- VERSION = "0.37.4"
5
+ VERSION = "0.37.5"
6
6
  end
7
7
  end
data/lib/ace/task.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "task/version"
6
6
  require "ace/support/items"
7
7
  require "ace/b36ts"
8
8
  require "ace/git"
9
+ require "ace/git/github"
9
10
 
10
11
  # Atoms
11
12
  require_relative "task/atoms/task_id_formatter"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-task
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.4
4
+ version: 0.37.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-05 00:00:00.000000000 Z
10
+ date: 2026-09-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-core
@@ -85,14 +85,28 @@ dependencies:
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.23'
88
+ version: '0.24'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '0.23'
95
+ version: '0.24'
96
+ - !ruby/object:Gem::Dependency
97
+ name: ace-git-github
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.1'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.1'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: ace-support-cli
98
112
  requirement: !ruby/object:Gem::Requirement