ace-assign 0.53.4 → 0.55.2
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 +4 -4
- data/.ace-defaults/assign/catalog/steps/split-subtree-root.step.yml +4 -2
- data/.ace-defaults/assign/config.yml +1 -0
- data/.ace-defaults/assign/presets/work-on-task.yml +3 -0
- data/CHANGELOG.md +25 -0
- data/docs/demo/fork-provider.cast +814 -937
- data/docs/demo/fork-provider.gif +0 -0
- data/docs/demo/fork-provider.recording.json +15 -17
- data/docs/demo/fork-provider.tape.yml +16 -4
- data/docs/usage.md +30 -7
- data/handbook/guides/fork-context.g.md +29 -5
- data/handbook/skills/as-assign-drive/SKILL.md +2 -2
- data/handbook/workflow-instructions/assign/drive.wf.md +109 -36
- data/handbook/workflow-instructions/assign/prepare.wf.md +5 -0
- data/lib/ace/assign/atoms/preset_expander.rb +4 -0
- data/lib/ace/assign/atoms/tree_formatter.rb +2 -2
- data/lib/ace/assign/cli/commands/add.rb +20 -11
- data/lib/ace/assign/cli/commands/assignment_target.rb +49 -18
- data/lib/ace/assign/cli/commands/create.rb +1 -1
- data/lib/ace/assign/cli/commands/fail.rb +1 -1
- data/lib/ace/assign/cli/commands/finish.rb +26 -5
- data/lib/ace/assign/cli/commands/fork_run.rb +56 -17
- data/lib/ace/assign/cli/commands/list.rb +4 -3
- data/lib/ace/assign/cli/commands/retry_cmd.rb +1 -1
- data/lib/ace/assign/cli/commands/status.rb +60 -34
- data/lib/ace/assign/cli/commands/step.rb +4 -4
- data/lib/ace/assign/cli.rb +1 -1
- data/lib/ace/assign/models/assignment_info.rb +33 -4
- data/lib/ace/assign/models/queue_state.rb +101 -39
- data/lib/ace/assign/models/step.rb +13 -3
- data/lib/ace/assign/molecules/fork_session_launcher.rb +76 -60
- data/lib/ace/assign/molecules/step_writer.rb +3 -3
- data/lib/ace/assign/molecules/tmux_control_surface_runner.rb +249 -0
- data/lib/ace/assign/organisms/assignment_executor.rb +132 -82
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +1 -0
- metadata +31 -17
- data/lib/ace/assign/molecules/tmux_fork_runner.rb +0 -191
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-assign
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.55.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-08-12 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -29,56 +29,56 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
32
|
+
version: '0.31'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
39
|
+
version: '0.31'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: ace-support-config
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0.
|
|
46
|
+
version: '0.17'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0.
|
|
53
|
+
version: '0.17'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: ace-support-nav
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
60
|
+
version: '0.28'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.
|
|
67
|
+
version: '0.28'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: ace-b36ts
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0.
|
|
74
|
+
version: '0.14'
|
|
75
75
|
type: :runtime
|
|
76
76
|
prerelease: false
|
|
77
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0.
|
|
81
|
+
version: '0.14'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
83
|
name: ace-support-markdown
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -99,42 +99,56 @@ dependencies:
|
|
|
99
99
|
requirements:
|
|
100
100
|
- - "~>"
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0.
|
|
102
|
+
version: '0.38'
|
|
103
103
|
type: :runtime
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0.
|
|
109
|
+
version: '0.38'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: ace-task
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
114
|
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '0.
|
|
116
|
+
version: '0.37'
|
|
117
117
|
type: :runtime
|
|
118
118
|
prerelease: false
|
|
119
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '0.
|
|
123
|
+
version: '0.37'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: ace-tmux
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.17'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.17'
|
|
124
138
|
- !ruby/object:Gem::Dependency
|
|
125
139
|
name: ace-support-test-helpers
|
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|
|
127
141
|
requirements:
|
|
128
142
|
- - "~>"
|
|
129
143
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: '0.
|
|
144
|
+
version: '0.14'
|
|
131
145
|
type: :development
|
|
132
146
|
prerelease: false
|
|
133
147
|
version_requirements: !ruby/object:Gem::Requirement
|
|
134
148
|
requirements:
|
|
135
149
|
- - "~>"
|
|
136
150
|
- !ruby/object:Gem::Version
|
|
137
|
-
version: '0.
|
|
151
|
+
version: '0.14'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: minitest
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -295,7 +309,7 @@ files:
|
|
|
295
309
|
- lib/ace/assign/molecules/skill_assign_source_resolver.rb
|
|
296
310
|
- lib/ace/assign/molecules/step_renumberer.rb
|
|
297
311
|
- lib/ace/assign/molecules/step_writer.rb
|
|
298
|
-
- lib/ace/assign/molecules/
|
|
312
|
+
- lib/ace/assign/molecules/tmux_control_surface_runner.rb
|
|
299
313
|
- lib/ace/assign/organisms/assignment_executor.rb
|
|
300
314
|
- lib/ace/assign/organisms/task_assignment_creator.rb
|
|
301
315
|
- lib/ace/assign/version.rb
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "open3"
|
|
5
|
-
require "shellwords"
|
|
6
|
-
require "yaml"
|
|
7
|
-
|
|
8
|
-
module Ace
|
|
9
|
-
module Assign
|
|
10
|
-
module Molecules
|
|
11
|
-
# Minimal tmux integration for forked subtree execution.
|
|
12
|
-
class TmuxForkRunner
|
|
13
|
-
DEFAULT_POLL_INTERVAL = 0.2
|
|
14
|
-
|
|
15
|
-
def initialize(tmux_binary: "tmux")
|
|
16
|
-
@tmux_binary = tmux_binary
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def tmux_context?
|
|
20
|
-
!current_session.to_s.strip.empty?
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def current_session
|
|
24
|
-
explicit = ENV["ACE_TMUX_SESSION"].to_s.strip
|
|
25
|
-
return explicit unless explicit.empty?
|
|
26
|
-
return nil if ENV["TMUX"].to_s.strip.empty?
|
|
27
|
-
|
|
28
|
-
capture([tmux_binary, "display-message", "-p", "#S"]).stdout
|
|
29
|
-
rescue
|
|
30
|
-
nil
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def current_window
|
|
34
|
-
explicit = ENV["ACE_ASSIGN_FORK_WINDOW"].to_s.strip
|
|
35
|
-
return explicit unless explicit.empty?
|
|
36
|
-
session = ENV["ACE_TMUX_SESSION"].to_s.strip
|
|
37
|
-
if !session.empty?
|
|
38
|
-
window = capture([tmux_binary, "display-message", "-t", "#{session}:", "-p", "#W"]).stdout
|
|
39
|
-
return window unless window.empty?
|
|
40
|
-
|
|
41
|
-
return active_window_name(session)
|
|
42
|
-
end
|
|
43
|
-
return nil if ENV["TMUX"].to_s.strip.empty?
|
|
44
|
-
|
|
45
|
-
capture([tmux_binary, "display-message", "-p", "#W"]).stdout
|
|
46
|
-
rescue
|
|
47
|
-
nil
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def fork_window_name(base_window)
|
|
51
|
-
base = base_window.to_s.strip
|
|
52
|
-
return base if base.end_with?("-fs")
|
|
53
|
-
|
|
54
|
-
"#{base}-fs"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def ensure_window(session:, name:, root:)
|
|
58
|
-
return {created: false, target: "#{session}:#{name}"} if window_exists?(session: session, name: name)
|
|
59
|
-
|
|
60
|
-
result = capture([tmux_binary, "new-window", "-t", "#{session}:", "-n", name, "-c", File.expand_path(root)])
|
|
61
|
-
raise Error, "Failed to create tmux fork window #{name}: #{result.stderr}" unless result.success?
|
|
62
|
-
|
|
63
|
-
{created: true, target: "#{session}:#{name}"}
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def prepare_pane(session:, window:, root:, keep_existing:)
|
|
67
|
-
target = "#{session}:#{window}"
|
|
68
|
-
if keep_existing
|
|
69
|
-
pane = first_pane(target)
|
|
70
|
-
set_pane_remain_on_exit(pane)
|
|
71
|
-
select_layout(target)
|
|
72
|
-
return pane
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
result = capture([tmux_binary, "split-window", "-t", target, "-c", File.expand_path(root), "-P", "-F", '#{pane_id}'])
|
|
76
|
-
raise Error, "Failed to create tmux fork pane in #{window}: #{result.stderr}" unless result.success?
|
|
77
|
-
|
|
78
|
-
pane = result.stdout
|
|
79
|
-
set_pane_remain_on_exit(pane)
|
|
80
|
-
select_layout(target)
|
|
81
|
-
pane
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def select_window(session:, window:)
|
|
85
|
-
run!([tmux_binary, "select-window", "-t", "#{session}:#{window}"], "select tmux fork window #{window}")
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def run_script_in_pane(pane_target:, script_path:)
|
|
89
|
-
command = "bash #{Shellwords.escape(File.expand_path(script_path))}"
|
|
90
|
-
run!([tmux_binary, "send-keys", "-t", pane_target, command, "Enter"], "send tmux fork command")
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def wait_for_completion(status_file:, timeout:)
|
|
94
|
-
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout.to_i
|
|
95
|
-
until File.exist?(status_file)
|
|
96
|
-
if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
97
|
-
raise Error, "Timed out waiting for tmux fork pane to finish (#{File.basename(status_file)})"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
sleep(DEFAULT_POLL_INTERVAL)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
status = File.read(status_file).strip
|
|
104
|
-
Integer(status)
|
|
105
|
-
rescue ArgumentError
|
|
106
|
-
raise Error, "Invalid tmux fork status file: #{status_file}"
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def merge_tmux_metadata(session_meta_file:, session:, window:, pane:)
|
|
110
|
-
data = if File.exist?(session_meta_file)
|
|
111
|
-
YAML.safe_load_file(session_meta_file) || {}
|
|
112
|
-
else
|
|
113
|
-
{}
|
|
114
|
-
end
|
|
115
|
-
data["launch_mode"] = "tmux"
|
|
116
|
-
data["tmux_session"] = session
|
|
117
|
-
data["tmux_window"] = window
|
|
118
|
-
data["tmux_pane_id"] = pane
|
|
119
|
-
File.write(session_meta_file, data.to_yaml)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
private
|
|
123
|
-
|
|
124
|
-
attr_reader :tmux_binary
|
|
125
|
-
|
|
126
|
-
def window_exists?(session:, name:)
|
|
127
|
-
result = capture([tmux_binary, "list-windows", "-t", session, "-F", '#{window_name}'])
|
|
128
|
-
return false unless result.success?
|
|
129
|
-
|
|
130
|
-
result.stdout_lines.any? { |value| value == name }
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def active_window_name(session)
|
|
134
|
-
result = capture([tmux_binary, "list-windows", "-t", session, "-F", '#{window_active} #{window_name}'])
|
|
135
|
-
return nil unless result.success?
|
|
136
|
-
|
|
137
|
-
active = result.stdout_lines.find { |line| line.start_with?("1 ") }
|
|
138
|
-
active&.sub(/\A1\s+/, "") || result.stdout_lines.first&.sub(/\A[01]\s+/, "")
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def first_pane(target)
|
|
142
|
-
result = capture([tmux_binary, "list-panes", "-t", target, "-F", '#{pane_id}'])
|
|
143
|
-
raise Error, "Failed to inspect panes for #{target}: #{result.stderr}" unless result.success?
|
|
144
|
-
|
|
145
|
-
pane = result.stdout_lines.first
|
|
146
|
-
raise Error, "No panes found for #{target}" if pane.to_s.empty?
|
|
147
|
-
|
|
148
|
-
pane
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def set_pane_remain_on_exit(pane_target)
|
|
152
|
-
run!([tmux_binary, "set-option", "-p", "-t", pane_target, "remain-on-exit", "on"], "enable remain-on-exit")
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def select_layout(target)
|
|
156
|
-
run!([tmux_binary, "select-layout", "-t", target, "tiled"], "apply tiled layout")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def run!(cmd, action)
|
|
160
|
-
result = capture(cmd)
|
|
161
|
-
return if result.success?
|
|
162
|
-
|
|
163
|
-
raise Error, "Failed to #{action}: #{result.stderr}"
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def capture(cmd)
|
|
167
|
-
stdout, stderr, status = Open3.capture3(*cmd)
|
|
168
|
-
Result.new(stdout: stdout, stderr: stderr, status: status)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
Result = Struct.new(:stdout, :stderr, :status, keyword_init: true) do
|
|
172
|
-
def success?
|
|
173
|
-
status.success?
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def stdout
|
|
177
|
-
self[:stdout].to_s.strip
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def stderr
|
|
181
|
-
self[:stderr].to_s.strip
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def stdout_lines
|
|
185
|
-
stdout.split("\n").map(&:strip).reject(&:empty?)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
end
|