ace-git-worktree 0.19.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 +7 -0
- data/.ace-defaults/git/worktree.yml +250 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-git-worktree.yml +19 -0
- data/CHANGELOG.md +957 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +14 -0
- data/docs/demo/ace-git-worktree-getting-started.gif +0 -0
- data/docs/demo/ace-git-worktree-getting-started.tape.yml +28 -0
- data/docs/demo/fixtures/README.md +3 -0
- data/docs/demo/fixtures/sample.txt +1 -0
- data/docs/getting-started.md +114 -0
- data/docs/handbook.md +38 -0
- data/docs/usage.md +334 -0
- data/exe/ace-git-worktree +24 -0
- data/handbook/agents/worktree.ag.md +189 -0
- data/handbook/skills/as-git-worktree/SKILL.md +27 -0
- data/handbook/skills/as-git-worktree-create/SKILL.md +21 -0
- data/handbook/skills/as-git-worktree-manage/SKILL.md +20 -0
- data/handbook/workflow-instructions/git/worktree-create.wf.md +262 -0
- data/handbook/workflow-instructions/git/worktree-manage.wf.md +384 -0
- data/handbook/workflow-instructions/git/worktree.wf.md +224 -0
- data/lib/ace/git/worktree/atoms/git_command.rb +121 -0
- data/lib/ace/git/worktree/atoms/path_expander.rb +189 -0
- data/lib/ace/git/worktree/atoms/slug_generator.rb +235 -0
- data/lib/ace/git/worktree/atoms/task_id_extractor.rb +91 -0
- data/lib/ace/git/worktree/cli/commands/config.rb +50 -0
- data/lib/ace/git/worktree/cli/commands/create.rb +80 -0
- data/lib/ace/git/worktree/cli/commands/list.rb +76 -0
- data/lib/ace/git/worktree/cli/commands/prune.rb +43 -0
- data/lib/ace/git/worktree/cli/commands/remove.rb +48 -0
- data/lib/ace/git/worktree/cli/commands/shared_helpers.rb +66 -0
- data/lib/ace/git/worktree/cli/commands/switch.rb +44 -0
- data/lib/ace/git/worktree/cli.rb +103 -0
- data/lib/ace/git/worktree/commands/config_command.rb +351 -0
- data/lib/ace/git/worktree/commands/create_command.rb +961 -0
- data/lib/ace/git/worktree/commands/list_command.rb +247 -0
- data/lib/ace/git/worktree/commands/prune_command.rb +260 -0
- data/lib/ace/git/worktree/commands/remove_command.rb +522 -0
- data/lib/ace/git/worktree/commands/switch_command.rb +249 -0
- data/lib/ace/git/worktree/configuration.rb +167 -0
- data/lib/ace/git/worktree/models/worktree_config.rb +502 -0
- data/lib/ace/git/worktree/models/worktree_info.rb +303 -0
- data/lib/ace/git/worktree/models/worktree_metadata.rb +294 -0
- data/lib/ace/git/worktree/molecules/config_loader.rb +125 -0
- data/lib/ace/git/worktree/molecules/current_task_linker.rb +136 -0
- data/lib/ace/git/worktree/molecules/hook_executor.rb +361 -0
- data/lib/ace/git/worktree/molecules/parent_task_resolver.rb +186 -0
- data/lib/ace/git/worktree/molecules/pr_creator.rb +253 -0
- data/lib/ace/git/worktree/molecules/task_committer.rb +329 -0
- data/lib/ace/git/worktree/molecules/task_fetcher.rb +244 -0
- data/lib/ace/git/worktree/molecules/task_pusher.rb +183 -0
- data/lib/ace/git/worktree/molecules/task_status_updater.rb +447 -0
- data/lib/ace/git/worktree/molecules/worktree_creator.rb +832 -0
- data/lib/ace/git/worktree/molecules/worktree_lister.rb +337 -0
- data/lib/ace/git/worktree/molecules/worktree_remover.rb +416 -0
- data/lib/ace/git/worktree/organisms/task_worktree_orchestrator.rb +906 -0
- data/lib/ace/git/worktree/organisms/worktree_manager.rb +714 -0
- data/lib/ace/git/worktree/version.rb +9 -0
- data/lib/ace/git/worktree.rb +215 -0
- metadata +218 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../atoms/git_command"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Git
|
|
7
|
+
module Worktree
|
|
8
|
+
module Molecules
|
|
9
|
+
# Task pusher molecule
|
|
10
|
+
#
|
|
11
|
+
# Pushes task changes to remote repository after commits.
|
|
12
|
+
# Used by TaskWorktreeOrchestrator to ensure task status updates
|
|
13
|
+
# are visible in PRs.
|
|
14
|
+
#
|
|
15
|
+
# @example Push to default remote
|
|
16
|
+
# pusher = TaskPusher.new
|
|
17
|
+
# result = pusher.push
|
|
18
|
+
#
|
|
19
|
+
# @example Push to specific remote and branch
|
|
20
|
+
# result = pusher.push(remote: "upstream", branch: "feature-branch")
|
|
21
|
+
class TaskPusher
|
|
22
|
+
# Default timeout for git push commands
|
|
23
|
+
DEFAULT_TIMEOUT = 60
|
|
24
|
+
|
|
25
|
+
# Initialize a new TaskPusher
|
|
26
|
+
#
|
|
27
|
+
# @param timeout [Integer] Command timeout in seconds
|
|
28
|
+
def initialize(timeout: DEFAULT_TIMEOUT)
|
|
29
|
+
@timeout = timeout
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Push current branch to remote
|
|
33
|
+
#
|
|
34
|
+
# @param remote [String] Remote name (default: "origin")
|
|
35
|
+
# @param branch [String, nil] Branch name (default: current branch)
|
|
36
|
+
# @param set_upstream [Boolean] Set upstream tracking (default: true)
|
|
37
|
+
# @return [Hash] Result with :success, :output, :error
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# pusher = TaskPusher.new
|
|
41
|
+
# result = pusher.push(remote: "origin")
|
|
42
|
+
# result[:success] # => true
|
|
43
|
+
def push(remote: "origin", branch: nil, set_upstream: true)
|
|
44
|
+
branch ||= current_branch
|
|
45
|
+
return failure_result("Could not determine current branch") unless branch
|
|
46
|
+
|
|
47
|
+
args = ["push"]
|
|
48
|
+
args << "-u" if set_upstream
|
|
49
|
+
args << remote
|
|
50
|
+
args << branch
|
|
51
|
+
|
|
52
|
+
result = Atoms::GitCommand.execute(*args, timeout: @timeout)
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
success: result[:success],
|
|
56
|
+
output: result[:output],
|
|
57
|
+
error: result[:error],
|
|
58
|
+
remote: remote,
|
|
59
|
+
branch: branch
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check if remote exists
|
|
64
|
+
#
|
|
65
|
+
# @param remote [String] Remote name to check
|
|
66
|
+
# @return [Boolean] true if remote exists
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# pusher.remote_exists?("origin") # => true
|
|
70
|
+
def remote_exists?(remote)
|
|
71
|
+
result = Atoms::GitCommand.execute("remote", "get-url", remote, timeout: 5)
|
|
72
|
+
result[:success]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Get current branch name
|
|
76
|
+
#
|
|
77
|
+
# @return [String, nil] Current branch name or nil if detached
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# pusher.current_branch # => "feature-branch"
|
|
81
|
+
def current_branch
|
|
82
|
+
result = Atoms::GitCommand.execute("branch", "--show-current", timeout: 5)
|
|
83
|
+
return nil unless result[:success]
|
|
84
|
+
|
|
85
|
+
branch = result[:output]&.strip
|
|
86
|
+
branch.empty? ? nil : branch
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Check if branch has upstream tracking
|
|
90
|
+
#
|
|
91
|
+
# @param branch [String, nil] Branch to check (default: current)
|
|
92
|
+
# @return [Boolean] true if branch has upstream
|
|
93
|
+
#
|
|
94
|
+
# @example
|
|
95
|
+
# pusher.has_upstream? # => true
|
|
96
|
+
def has_upstream?(branch = nil)
|
|
97
|
+
branch ||= current_branch
|
|
98
|
+
return false unless branch
|
|
99
|
+
|
|
100
|
+
result = Atoms::GitCommand.execute(
|
|
101
|
+
"rev-parse", "--abbrev-ref", "#{branch}@{upstream}",
|
|
102
|
+
timeout: 5
|
|
103
|
+
)
|
|
104
|
+
result[:success]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Set upstream tracking for a branch
|
|
108
|
+
#
|
|
109
|
+
# Uses `git branch --set-upstream-to` to configure tracking without pushing.
|
|
110
|
+
# Useful when the remote branch already exists or push is not desired.
|
|
111
|
+
#
|
|
112
|
+
# @param branch [String, nil] Branch to configure (default: current)
|
|
113
|
+
# @param remote [String] Remote name (default: "origin")
|
|
114
|
+
# @return [Hash] Result with :success, :output, :error, :remote, :branch
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# pusher.set_upstream(branch: "feature", remote: "origin")
|
|
118
|
+
# # => { success: true, branch: "feature", remote: "origin", ... }
|
|
119
|
+
def set_upstream(branch: nil, remote: "origin")
|
|
120
|
+
branch ||= current_branch
|
|
121
|
+
return failure_result("Could not determine current branch") unless branch
|
|
122
|
+
|
|
123
|
+
result = Atoms::GitCommand.execute(
|
|
124
|
+
"branch", "--set-upstream-to=#{remote}/#{branch}", branch,
|
|
125
|
+
timeout: @timeout
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
success: result[:success],
|
|
130
|
+
output: result[:output],
|
|
131
|
+
error: result[:error],
|
|
132
|
+
remote: remote,
|
|
133
|
+
branch: branch
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Get the upstream remote/branch for current branch
|
|
138
|
+
#
|
|
139
|
+
# @param branch [String, nil] Branch to check (default: current)
|
|
140
|
+
# @return [Hash, nil] Hash with :remote and :branch keys, or nil
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# pusher.get_upstream # => { remote: "origin", branch: "main" }
|
|
144
|
+
def get_upstream(branch = nil)
|
|
145
|
+
branch ||= current_branch
|
|
146
|
+
return nil unless branch
|
|
147
|
+
|
|
148
|
+
result = Atoms::GitCommand.execute(
|
|
149
|
+
"rev-parse", "--abbrev-ref", "#{branch}@{upstream}",
|
|
150
|
+
timeout: 5
|
|
151
|
+
)
|
|
152
|
+
return nil unless result[:success]
|
|
153
|
+
|
|
154
|
+
upstream = result[:output]&.strip
|
|
155
|
+
return nil if upstream.nil? || upstream.empty?
|
|
156
|
+
|
|
157
|
+
# Parse "origin/branch-name" format
|
|
158
|
+
parts = upstream.split("/", 2)
|
|
159
|
+
return nil if parts.length < 2
|
|
160
|
+
|
|
161
|
+
{remote: parts[0], branch: parts[1]}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
# Create a failure result hash
|
|
167
|
+
#
|
|
168
|
+
# @param message [String] Error message
|
|
169
|
+
# @return [Hash] Failure result
|
|
170
|
+
def failure_result(message)
|
|
171
|
+
{
|
|
172
|
+
success: false,
|
|
173
|
+
output: "",
|
|
174
|
+
error: message,
|
|
175
|
+
remote: nil,
|
|
176
|
+
branch: nil
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Try to require ace-task API for direct integration (organism level only)
|
|
4
|
+
begin
|
|
5
|
+
require "ace/task/organisms/task_manager"
|
|
6
|
+
rescue LoadError
|
|
7
|
+
# ace-task not available - will fall back to CLI
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "../atoms/task_id_extractor"
|
|
11
|
+
|
|
12
|
+
module Ace
|
|
13
|
+
module Git
|
|
14
|
+
module Worktree
|
|
15
|
+
module Molecules
|
|
16
|
+
# Task status updater molecule
|
|
17
|
+
#
|
|
18
|
+
# Updates task status using ace-task Ruby API or CLI commands.
|
|
19
|
+
# Provides methods for marking tasks as in-progress, done, etc.
|
|
20
|
+
# All methods return a result hash with :success and :message keys.
|
|
21
|
+
#
|
|
22
|
+
# @example Mark task as in-progress
|
|
23
|
+
# updater = TaskStatusUpdater.new
|
|
24
|
+
# result = updater.mark_in_progress("8pp.t.q7w")
|
|
25
|
+
# # => { success: true, message: "Task status updated to in-progress" }
|
|
26
|
+
#
|
|
27
|
+
# @example Update with custom status
|
|
28
|
+
# result = updater.update_status("8pp.t.q7w", "done")
|
|
29
|
+
class TaskStatusUpdater
|
|
30
|
+
# Default timeout for ace-task commands
|
|
31
|
+
DEFAULT_TIMEOUT = 10
|
|
32
|
+
|
|
33
|
+
# Initialize a new TaskStatusUpdater
|
|
34
|
+
#
|
|
35
|
+
# @param timeout [Integer] Command timeout in seconds
|
|
36
|
+
def initialize(timeout: DEFAULT_TIMEOUT)
|
|
37
|
+
@timeout = timeout
|
|
38
|
+
@project_root = ENV["PROJECT_ROOT_PATH"] || Dir.pwd
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Mark task as in-progress
|
|
42
|
+
#
|
|
43
|
+
# @param task_ref [String] Task reference
|
|
44
|
+
# @return [Hash] Result with :success and :message keys
|
|
45
|
+
def mark_in_progress(task_ref)
|
|
46
|
+
update_status(task_ref, "in-progress")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Mark task as done
|
|
50
|
+
#
|
|
51
|
+
# @param task_ref [String] Task reference
|
|
52
|
+
# @return [Hash] Result with :success and :message keys
|
|
53
|
+
def mark_done(task_ref)
|
|
54
|
+
update_status(task_ref, "done")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Mark task as blocked
|
|
58
|
+
#
|
|
59
|
+
# @param task_ref [String] Task reference
|
|
60
|
+
# @return [Hash] Result with :success and :message keys
|
|
61
|
+
def mark_blocked(task_ref)
|
|
62
|
+
update_status(task_ref, "blocked")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Update task to custom status
|
|
66
|
+
#
|
|
67
|
+
# @param task_ref [String] Task reference
|
|
68
|
+
# @param status [String] New status value
|
|
69
|
+
# @return [Hash] Result with :success and :message keys
|
|
70
|
+
def update_status(task_ref, status)
|
|
71
|
+
return {success: false, message: "Task reference is required"} if task_ref.nil? || task_ref.empty?
|
|
72
|
+
return {success: false, message: "Status is required"} if status.nil? || status.empty?
|
|
73
|
+
|
|
74
|
+
puts "DEBUG: TaskStatusUpdater.update_status called with task_ref=#{task_ref}, status=#{status}" if ENV["DEBUG"]
|
|
75
|
+
puts "DEBUG: use_ruby_api? = #{use_ruby_api?}" if ENV["DEBUG"]
|
|
76
|
+
|
|
77
|
+
# Try Ruby API first (preferred in mono-repo)
|
|
78
|
+
if use_ruby_api?
|
|
79
|
+
puts "DEBUG: Using Ruby API for status update" if ENV["DEBUG"]
|
|
80
|
+
result = update_status_via_api(task_ref, status)
|
|
81
|
+
puts "DEBUG: Ruby API result: #{result}" if ENV["DEBUG"]
|
|
82
|
+
return result
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Fallback to CLI for standalone installations
|
|
86
|
+
puts "DEBUG: Using CLI for status update" if ENV["DEBUG"]
|
|
87
|
+
result = update_status_via_cli(task_ref, status)
|
|
88
|
+
puts "DEBUG: CLI result: #{result}" if ENV["DEBUG"]
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Update task priority
|
|
93
|
+
#
|
|
94
|
+
# @param task_ref [String] Task reference
|
|
95
|
+
# @param priority [String] New priority (high, medium, low)
|
|
96
|
+
# @return [Boolean] true if priority was updated successfully
|
|
97
|
+
def update_priority(task_ref, priority)
|
|
98
|
+
return false unless %w[high medium low].include?(priority.to_s)
|
|
99
|
+
|
|
100
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
101
|
+
return false unless normalized_ref
|
|
102
|
+
|
|
103
|
+
if use_ruby_api?
|
|
104
|
+
return update_fields_via_api(normalized_ref, {"priority" => priority})
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "priority=#{priority}")
|
|
108
|
+
result[:success]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Update task estimate
|
|
112
|
+
#
|
|
113
|
+
# @param task_ref [String] Task reference
|
|
114
|
+
# @param estimate [String] New estimate (e.g., "2h", "1-2 days")
|
|
115
|
+
# @return [Boolean] true if estimate was updated successfully
|
|
116
|
+
def update_estimate(task_ref, estimate)
|
|
117
|
+
return false if estimate.nil? || estimate.empty?
|
|
118
|
+
|
|
119
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
120
|
+
return false unless normalized_ref
|
|
121
|
+
|
|
122
|
+
if use_ruby_api?
|
|
123
|
+
return update_fields_via_api(normalized_ref, {"estimate" => estimate})
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "estimate=#{estimate}")
|
|
127
|
+
result[:success]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Add worktree metadata to task
|
|
131
|
+
#
|
|
132
|
+
# @param task_ref [String] Task reference
|
|
133
|
+
# @param worktree_metadata [WorktreeMetadata] Worktree metadata to add
|
|
134
|
+
# @return [Boolean] true if metadata was added successfully
|
|
135
|
+
def add_worktree_metadata(task_ref, worktree_metadata)
|
|
136
|
+
return false unless worktree_metadata.is_a?(Models::WorktreeMetadata)
|
|
137
|
+
|
|
138
|
+
# Try Ruby API first (preferred in mono-repo)
|
|
139
|
+
if use_ruby_api?
|
|
140
|
+
return add_worktree_metadata_via_api(task_ref, worktree_metadata)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Fallback to CLI for standalone installations
|
|
144
|
+
add_worktree_metadata_via_cli(task_ref, worktree_metadata)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Add PR metadata to task
|
|
148
|
+
#
|
|
149
|
+
# @param task_ref [String] Task reference
|
|
150
|
+
# @param pr_data [Hash] PR data with :number, :url, :created_at
|
|
151
|
+
# @return [Boolean] true if metadata was added successfully
|
|
152
|
+
def add_pr_metadata(task_ref, pr_data)
|
|
153
|
+
return false unless pr_data.is_a?(Hash)
|
|
154
|
+
return false unless pr_data[:number] && pr_data[:url]
|
|
155
|
+
|
|
156
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
157
|
+
return false unless normalized_ref
|
|
158
|
+
|
|
159
|
+
# Try Ruby API first (preferred in mono-repo)
|
|
160
|
+
if use_ruby_api?
|
|
161
|
+
return add_pr_metadata_via_api(task_ref, pr_data)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Fallback to CLI for standalone installations
|
|
165
|
+
add_pr_metadata_via_cli(task_ref, pr_data)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Add started_at timestamp to task
|
|
169
|
+
#
|
|
170
|
+
# @param task_ref [String] Task reference
|
|
171
|
+
# @return [Boolean] true if timestamp was added successfully
|
|
172
|
+
def add_started_at_timestamp(task_ref)
|
|
173
|
+
started_at = Time.now
|
|
174
|
+
|
|
175
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
176
|
+
return false unless normalized_ref
|
|
177
|
+
|
|
178
|
+
# Try Ruby API first (preferred in mono-repo)
|
|
179
|
+
if use_ruby_api?
|
|
180
|
+
return add_started_at_via_api(task_ref, started_at)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Fallback to CLI for standalone installations
|
|
184
|
+
add_started_at_via_cli(task_ref, started_at)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Remove worktree metadata from task
|
|
188
|
+
#
|
|
189
|
+
# @param task_ref [String] Task reference
|
|
190
|
+
# @return [Boolean] true if metadata was removed successfully
|
|
191
|
+
def remove_worktree_metadata(task_ref)
|
|
192
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
193
|
+
return false unless normalized_ref
|
|
194
|
+
|
|
195
|
+
false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Get current task status
|
|
199
|
+
#
|
|
200
|
+
# @param task_ref [String] Task reference
|
|
201
|
+
# @return [String, nil] Current status or nil if not found
|
|
202
|
+
def get_status(task_ref)
|
|
203
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
204
|
+
return nil unless normalized_ref
|
|
205
|
+
|
|
206
|
+
# Fetch task metadata
|
|
207
|
+
fetcher = TaskFetcher.new
|
|
208
|
+
task = fetcher.fetch(normalized_ref)
|
|
209
|
+
task ? task[:status] : nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Check if ace-task update command is available
|
|
213
|
+
#
|
|
214
|
+
# @return [Boolean] true if update command is available
|
|
215
|
+
def update_command_available?
|
|
216
|
+
result = execute_ace_task_command("update", "--help", timeout: 5)
|
|
217
|
+
result[:success]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
# Check if we can use Ruby API
|
|
223
|
+
#
|
|
224
|
+
# @return [Boolean] true if Ruby API is available
|
|
225
|
+
def use_ruby_api?
|
|
226
|
+
defined?(Ace::Task::Organisms::TaskManager)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Update task status using Ruby API
|
|
230
|
+
#
|
|
231
|
+
# @param task_ref [String] Task reference
|
|
232
|
+
# @param status [String] New status
|
|
233
|
+
# @return [Hash] Result with :success and :message keys
|
|
234
|
+
def update_status_via_api(task_ref, status)
|
|
235
|
+
task_manager = Ace::Task::Organisms::TaskManager.new
|
|
236
|
+
result = task_manager.update(task_ref, set: {"status" => status})
|
|
237
|
+
|
|
238
|
+
puts "DEBUG: TaskManager result: #{result.inspect}" if ENV["DEBUG"]
|
|
239
|
+
|
|
240
|
+
if result
|
|
241
|
+
{success: true, message: "Task status updated to #{status}"}
|
|
242
|
+
else
|
|
243
|
+
puts "DEBUG: TaskManager returned nil" if ENV["DEBUG"]
|
|
244
|
+
{success: false, message: "Failed to update task status"}
|
|
245
|
+
end
|
|
246
|
+
rescue => e
|
|
247
|
+
puts "DEBUG: TaskManager exception: #{e.message}" if ENV["DEBUG"]
|
|
248
|
+
# Fall back to CLI on API error
|
|
249
|
+
update_status_via_cli(task_ref, status)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Update task status using CLI
|
|
253
|
+
#
|
|
254
|
+
# @param task_ref [String] Task reference
|
|
255
|
+
# @param status [String] New status
|
|
256
|
+
# @return [Hash] Result with :success and :message keys
|
|
257
|
+
def update_status_via_cli(task_ref, status)
|
|
258
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
259
|
+
return {success: false, message: "Invalid task reference"} unless normalized_ref
|
|
260
|
+
|
|
261
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "status=#{status}")
|
|
262
|
+
if result[:success]
|
|
263
|
+
{success: true, message: "Task status updated to #{status}"}
|
|
264
|
+
else
|
|
265
|
+
{success: false, message: result[:error] || "Failed to update task status"}
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Update fields using Ruby API
|
|
270
|
+
#
|
|
271
|
+
# @param task_ref [String] Task reference
|
|
272
|
+
# @param fields [Hash] Fields to update
|
|
273
|
+
# @return [Boolean] true if successful
|
|
274
|
+
def update_fields_via_api(task_ref, fields)
|
|
275
|
+
task_manager = Ace::Task::Organisms::TaskManager.new
|
|
276
|
+
result = task_manager.update(task_ref, set: fields)
|
|
277
|
+
!result.nil?
|
|
278
|
+
rescue
|
|
279
|
+
false
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Add worktree metadata using Ruby API
|
|
283
|
+
#
|
|
284
|
+
# @param task_ref [String] Task reference
|
|
285
|
+
# @param worktree_metadata [WorktreeMetadata] Worktree metadata
|
|
286
|
+
# @return [Boolean] true if successful
|
|
287
|
+
def add_worktree_metadata_via_api(task_ref, worktree_metadata)
|
|
288
|
+
task_manager = Ace::Task::Organisms::TaskManager.new
|
|
289
|
+
|
|
290
|
+
# Convert worktree metadata to field updates
|
|
291
|
+
metadata_hash = worktree_metadata.to_h
|
|
292
|
+
field_updates = {}
|
|
293
|
+
metadata_hash.each do |field, value|
|
|
294
|
+
field_updates["worktree.#{field}"] = value.to_s
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
result = task_manager.update(task_ref, set: field_updates)
|
|
298
|
+
!result.nil?
|
|
299
|
+
rescue
|
|
300
|
+
# Fall back to CLI on API error
|
|
301
|
+
add_worktree_metadata_via_cli(task_ref, worktree_metadata)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Add worktree metadata using CLI
|
|
305
|
+
#
|
|
306
|
+
# @param task_ref [String] Task reference
|
|
307
|
+
# @param worktree_metadata [WorktreeMetadata] Worktree metadata
|
|
308
|
+
# @return [Boolean] true if successful
|
|
309
|
+
def add_worktree_metadata_via_cli(task_ref, worktree_metadata)
|
|
310
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
311
|
+
return false unless normalized_ref
|
|
312
|
+
|
|
313
|
+
metadata_hash = worktree_metadata.to_h
|
|
314
|
+
success = true
|
|
315
|
+
|
|
316
|
+
metadata_hash.each do |field, value|
|
|
317
|
+
field_path = "worktree.#{field}"
|
|
318
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "#{field_path}=#{value}")
|
|
319
|
+
success &&= result[:success]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
success
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Add PR metadata using Ruby API
|
|
326
|
+
#
|
|
327
|
+
# @param task_ref [String] Task reference
|
|
328
|
+
# @param pr_data [Hash] PR data
|
|
329
|
+
# @return [Boolean] true if successful
|
|
330
|
+
def add_pr_metadata_via_api(task_ref, pr_data)
|
|
331
|
+
task_manager = Ace::Task::Organisms::TaskManager.new
|
|
332
|
+
|
|
333
|
+
field_updates = {
|
|
334
|
+
"pr.number" => pr_data[:number].to_s,
|
|
335
|
+
"pr.url" => pr_data[:url].to_s
|
|
336
|
+
}
|
|
337
|
+
field_updates["pr.created_at"] = pr_data[:created_at].iso8601 if pr_data[:created_at]
|
|
338
|
+
|
|
339
|
+
result = task_manager.update(task_ref, set: field_updates)
|
|
340
|
+
!result.nil?
|
|
341
|
+
rescue
|
|
342
|
+
# Fall back to CLI on API error
|
|
343
|
+
add_pr_metadata_via_cli(task_ref, pr_data)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Add PR metadata using CLI
|
|
347
|
+
#
|
|
348
|
+
# @param task_ref [String] Task reference
|
|
349
|
+
# @param pr_data [Hash] PR data
|
|
350
|
+
# @return [Boolean] true if successful
|
|
351
|
+
def add_pr_metadata_via_cli(task_ref, pr_data)
|
|
352
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
353
|
+
return false unless normalized_ref
|
|
354
|
+
|
|
355
|
+
success = true
|
|
356
|
+
|
|
357
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "pr.number=#{pr_data[:number]}")
|
|
358
|
+
success &&= result[:success]
|
|
359
|
+
|
|
360
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "pr.url=#{pr_data[:url]}")
|
|
361
|
+
success &&= result[:success]
|
|
362
|
+
|
|
363
|
+
if pr_data[:created_at]
|
|
364
|
+
timestamp = pr_data[:created_at].respond_to?(:iso8601) ? pr_data[:created_at].iso8601 : pr_data[:created_at].to_s
|
|
365
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "pr.created_at=#{timestamp}")
|
|
366
|
+
success &&= result[:success]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
success
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Add started_at timestamp using Ruby API
|
|
373
|
+
#
|
|
374
|
+
# @param task_ref [String] Task reference
|
|
375
|
+
# @param started_at [Time] Start timestamp
|
|
376
|
+
# @return [Boolean] true if successful
|
|
377
|
+
def add_started_at_via_api(task_ref, started_at)
|
|
378
|
+
task_manager = Ace::Task::Organisms::TaskManager.new
|
|
379
|
+
|
|
380
|
+
field_updates = {"started_at" => started_at.iso8601}
|
|
381
|
+
|
|
382
|
+
result = task_manager.update(task_ref, set: field_updates)
|
|
383
|
+
!result.nil?
|
|
384
|
+
rescue
|
|
385
|
+
# Fall back to CLI on API error
|
|
386
|
+
add_started_at_via_cli(task_ref, started_at)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Add started_at timestamp using CLI
|
|
390
|
+
#
|
|
391
|
+
# @param task_ref [String] Task reference
|
|
392
|
+
# @param started_at [Time] Start timestamp
|
|
393
|
+
# @return [Boolean] true if successful
|
|
394
|
+
def add_started_at_via_cli(task_ref, started_at)
|
|
395
|
+
normalized_ref = normalize_task_reference(task_ref)
|
|
396
|
+
return false unless normalized_ref
|
|
397
|
+
|
|
398
|
+
timestamp = started_at.respond_to?(:iso8601) ? started_at.iso8601 : started_at.to_s
|
|
399
|
+
result = execute_ace_task_command("update", normalized_ref, "--set", "started_at=#{timestamp}")
|
|
400
|
+
result[:success]
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Normalize task reference to a standard format
|
|
404
|
+
#
|
|
405
|
+
# @param task_ref [String] Input task reference
|
|
406
|
+
# @return [String, nil] Normalized reference or nil if invalid
|
|
407
|
+
def normalize_task_reference(task_ref)
|
|
408
|
+
Atoms::TaskIDExtractor.normalize(task_ref)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Execute ace-task command
|
|
412
|
+
#
|
|
413
|
+
# @param args [Array<String>] Command arguments
|
|
414
|
+
# @return [Hash] Result with :success, :output, :error, :exit_code
|
|
415
|
+
def execute_ace_task_command(*args)
|
|
416
|
+
require "open3"
|
|
417
|
+
|
|
418
|
+
full_command = ["ace-task"] + args
|
|
419
|
+
|
|
420
|
+
stdout, stderr, status = Open3.capture3(*full_command, timeout: @timeout)
|
|
421
|
+
|
|
422
|
+
{
|
|
423
|
+
success: status.success?,
|
|
424
|
+
output: stdout.to_s,
|
|
425
|
+
error: stderr.to_s,
|
|
426
|
+
exit_code: status.exitstatus
|
|
427
|
+
}
|
|
428
|
+
rescue Open3::CommandTimeout
|
|
429
|
+
{
|
|
430
|
+
success: false,
|
|
431
|
+
output: "",
|
|
432
|
+
error: "ace-task command timed out after #{@timeout} seconds",
|
|
433
|
+
exit_code: 124
|
|
434
|
+
}
|
|
435
|
+
rescue => e
|
|
436
|
+
{
|
|
437
|
+
success: false,
|
|
438
|
+
output: "",
|
|
439
|
+
error: "ace-task command failed: #{e.message}",
|
|
440
|
+
exit_code: 1
|
|
441
|
+
}
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|