ace-assign 0.37.0 → 0.40.4

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.
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ace/task"
4
+ require "fileutils"
5
+ require "securerandom"
6
+ require "tempfile"
7
+ require "yaml"
8
+
9
+ require_relative "../atoms/preset_loader"
10
+ require_relative "../atoms/preset_expander"
11
+
12
+ module Ace
13
+ module Assign
14
+ module Organisms
15
+ class TaskAssignmentCreator
16
+ DEFAULT_PRESET = "work-on-task"
17
+ SUBTASK_PATTERN = /^[0-9a-z]{3}\.[a-z]\.[0-9a-z]{3}\.[a-z0-9]$/
18
+
19
+ def initialize(task_manager: nil, executor: nil)
20
+ @task_manager = task_manager || Ace::Task::Organisms::TaskManager.new
21
+ @executor = executor || AssignmentExecutor.new
22
+ end
23
+
24
+ def call(task_refs:, preset_name: DEFAULT_PRESET, primary_task_ref: nil)
25
+ requested_refs = normalize_requested_refs(task_refs)
26
+ raise Ace::Support::Cli::Error, "--task requires at least one task reference" if requested_refs.empty?
27
+
28
+ preset = Atoms::PresetLoader.load(preset_name)
29
+ resolved_refs, skipped_terminal = resolve_requested_refs(requested_refs)
30
+
31
+ if resolved_refs.empty?
32
+ raise Ace::Support::Cli::Error,
33
+ "All requested tasks are already terminal (done/skipped/cancelled): #{skipped_terminal.join(', ')}. No assignment created."
34
+ end
35
+
36
+ primary_ref = primary_task_ref.to_s.strip
37
+ primary_ref = resolved_refs.first[:ref] if primary_ref.empty?
38
+
39
+ guard_multi_task_preset!(preset_name, preset, resolved_refs.length)
40
+
41
+ expanded_taskrefs = if preset_supports_taskrefs?(preset)
42
+ expand_task_refs_in_order(resolved_refs)
43
+ else
44
+ [primary_ref]
45
+ end
46
+
47
+ params = build_parameters(preset, primary_ref, expanded_taskrefs)
48
+ validate_parameters!(preset, params)
49
+
50
+ steps = Atoms::PresetExpander.expand(preset, params)
51
+ job_path = write_job_file(
52
+ session_name: "#{preset_name}-#{primary_ref}",
53
+ description: preset["description"],
54
+ steps: steps
55
+ )
56
+
57
+ result = @executor.start(job_path)
58
+ result.merge(
59
+ skipped_terminal: skipped_terminal,
60
+ primary_ref: primary_ref,
61
+ task_refs: expanded_taskrefs,
62
+ job_path: job_path
63
+ )
64
+ end
65
+
66
+ private
67
+
68
+ def normalize_requested_refs(task_refs)
69
+ Array(task_refs)
70
+ .flat_map { |entry| entry.to_s.split(",") }
71
+ .map(&:strip)
72
+ .reject(&:empty?)
73
+ end
74
+
75
+ def resolve_requested_refs(requested_refs)
76
+ resolved = []
77
+ skipped_terminal = []
78
+
79
+ requested_refs.each do |ref|
80
+ task = @task_manager.show(ref.to_s)
81
+ raise Ace::Support::Cli::Error, "Task not found: #{ref}" unless task
82
+
83
+ if task.status.to_s == "draft"
84
+ raise Ace::Support::Cli::Error,
85
+ "Task #{ref} has status 'draft' and has not been reviewed. " \
86
+ "Review the spec first with /as-task-review #{ref} " \
87
+ "(or ace-bundle wfi://task/review), then retry."
88
+ end
89
+
90
+ if Ace::Task::Atoms::TaskValidationRules.terminal_status?(task.status.to_s)
91
+ skipped_terminal << ref.to_s
92
+ next
93
+ end
94
+
95
+ resolved << {ref: ref.to_s, task: task, is_subtask: subtask_ref?(ref)}
96
+ end
97
+
98
+ [resolved, skipped_terminal]
99
+ end
100
+
101
+ def subtask_ref?(ref)
102
+ ref.to_s.match?(SUBTASK_PATTERN)
103
+ end
104
+
105
+ def preset_supports_taskrefs?(preset)
106
+ (preset["parameters"] || {}).key?("taskrefs")
107
+ end
108
+
109
+ def guard_multi_task_preset!(preset_name, preset, input_count)
110
+ return unless input_count > 1 && !preset_supports_taskrefs?(preset)
111
+
112
+ raise Ace::Support::Cli::Error,
113
+ "Preset '#{preset_name}' accepts only single taskref. " \
114
+ "Use a preset with `taskrefs` (e.g., --preset work-on-task)."
115
+ end
116
+
117
+ def expand_task_refs_in_order(resolved_refs)
118
+ resolved_refs.flat_map do |entry|
119
+ ref = entry[:ref]
120
+ task = entry[:task]
121
+
122
+ if entry[:is_subtask]
123
+ [ref]
124
+ elsif task.respond_to?(:subtasks) && task.subtasks&.any?
125
+ active_subtasks = task.subtasks
126
+ .reject { |st| Ace::Task::Atoms::TaskValidationRules.terminal_status?(st.status.to_s) }
127
+ .map(&:id)
128
+ active_subtasks.empty? ? [ref] : active_subtasks
129
+ else
130
+ [ref]
131
+ end
132
+ end
133
+ end
134
+
135
+ def build_parameters(preset, primary_ref, task_refs)
136
+ param_defs = preset["parameters"] || {}
137
+ params = {}
138
+ params["taskref"] = primary_ref if param_defs.key?("taskref")
139
+ params["taskrefs"] = task_refs if param_defs.key?("taskrefs")
140
+ params
141
+ end
142
+
143
+ def validate_parameters!(preset, params)
144
+ errors = Atoms::PresetExpander.validate_parameters(preset, params)
145
+ return if errors.empty?
146
+
147
+ raise Ace::Support::Cli::Error, errors.join(", ")
148
+ end
149
+
150
+ def write_job_file(session_name:, description:, steps:)
151
+ job = {
152
+ "session" => {
153
+ "name" => session_name,
154
+ "description" => description || "Prepared by ace-assign create --task"
155
+ },
156
+ "steps" => steps
157
+ }
158
+
159
+ dir = File.join(Ace::Assign.cache_dir, "jobs")
160
+ FileUtils.mkdir_p(dir)
161
+ path = File.join(dir, "#{session_name}-#{SecureRandom.hex(4)}-job.yml")
162
+ payload = YAML.dump(job)
163
+
164
+ Tempfile.create(["#{session_name}-job", ".yml"], dir) do |tmp|
165
+ tmp.write(payload)
166
+ tmp.flush
167
+ tmp.fsync
168
+ FileUtils.mv(tmp.path, path)
169
+ end
170
+
171
+ path
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Assign
5
- VERSION = "0.37.0"
5
+ VERSION = '0.40.4'
6
6
  end
7
7
  end
data/lib/ace/assign.rb CHANGED
@@ -9,6 +9,7 @@ require "pathname"
9
9
 
10
10
  # CLI and commands
11
11
  require_relative "assign/cli"
12
+ require_relative "assign/organisms/task_assignment_creator"
12
13
 
13
14
  module Ace
14
15
  module Assign
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-assign
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.0
4
+ version: 0.40.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
@@ -15,112 +15,126 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.3'
18
+ version: '0.6'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.3'
25
+ version: '0.6'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: ace-support-core
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.25'
32
+ version: '0.29'
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.25'
39
+ version: '0.29'
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.8'
46
+ version: '0.9'
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.8'
53
+ version: '0.9'
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.7'
60
+ version: '0.25'
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.7'
67
+ version: '0.25'
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.7'
74
+ version: '0.13'
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.7'
81
+ version: '0.13'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ace-support-markdown
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.2'
88
+ version: '0.3'
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.2'
95
+ version: '0.3'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: ace-llm
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '0.26'
102
+ version: '0.30'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.30'
110
+ - !ruby/object:Gem::Dependency
111
+ name: ace-task
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.31'
103
117
  type: :runtime
104
118
  prerelease: false
105
119
  version_requirements: !ruby/object:Gem::Requirement
106
120
  requirements:
107
121
  - - "~>"
108
122
  - !ruby/object:Gem::Version
109
- version: '0.26'
123
+ version: '0.31'
110
124
  - !ruby/object:Gem::Dependency
111
125
  name: ace-support-test-helpers
112
126
  requirement: !ruby/object:Gem::Requirement
113
127
  requirements:
114
128
  - - "~>"
115
129
  - !ruby/object:Gem::Version
116
- version: '0.12'
130
+ version: '0.13'
117
131
  type: :development
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - "~>"
122
136
  - !ruby/object:Gem::Version
123
- version: '0.12'
137
+ version: '0.13'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: minitest
126
140
  requirement: !ruby/object:Gem::Requirement
@@ -179,6 +193,7 @@ files:
179
193
  - ".ace-defaults/assign/catalog/steps/pre-commit-review.step.yml"
180
194
  - ".ace-defaults/assign/catalog/steps/push-to-remote.step.yml"
181
195
  - ".ace-defaults/assign/catalog/steps/rebase-with-main.step.yml"
196
+ - ".ace-defaults/assign/catalog/steps/record-demo.step.yml"
182
197
  - ".ace-defaults/assign/catalog/steps/reflect-and-refactor.step.yml"
183
198
  - ".ace-defaults/assign/catalog/steps/release-minor.step.yml"
184
199
  - ".ace-defaults/assign/catalog/steps/release.step.yml"
@@ -212,6 +227,7 @@ files:
212
227
  - docs/usage.md
213
228
  - exe/ace-assign
214
229
  - handbook/guides/fork-context.g.md
230
+ - handbook/skills/as-assign-add-task/SKILL.md
215
231
  - handbook/skills/as-assign-compose/SKILL.md
216
232
  - handbook/skills/as-assign-create/SKILL.md
217
233
  - handbook/skills/as-assign-drive/SKILL.md
@@ -219,6 +235,7 @@ files:
219
235
  - handbook/skills/as-assign-recover-fork/SKILL.md
220
236
  - handbook/skills/as-assign-run-in-batches/SKILL.md
221
237
  - handbook/skills/as-assign-start/SKILL.md
238
+ - handbook/workflow-instructions/assign/add-task.wf.md
222
239
  - handbook/workflow-instructions/assign/compose.wf.md
223
240
  - handbook/workflow-instructions/assign/create.wf.md
224
241
  - handbook/workflow-instructions/assign/drive.wf.md
@@ -232,6 +249,8 @@ files:
232
249
  - lib/ace/assign/atoms/composition_rules.rb
233
250
  - lib/ace/assign/atoms/number_generator.rb
234
251
  - lib/ace/assign/atoms/preset_expander.rb
252
+ - lib/ace/assign/atoms/preset_loader.rb
253
+ - lib/ace/assign/atoms/preset_step_resolver.rb
235
254
  - lib/ace/assign/atoms/step_file_parser.rb
236
255
  - lib/ace/assign/atoms/step_numbering.rb
237
256
  - lib/ace/assign/atoms/step_sorter.rb
@@ -255,18 +274,20 @@ files:
255
274
  - lib/ace/assign/molecules/assignment_discoverer.rb
256
275
  - lib/ace/assign/molecules/assignment_manager.rb
257
276
  - lib/ace/assign/molecules/fork_session_launcher.rb
277
+ - lib/ace/assign/molecules/preset_inferrer.rb
258
278
  - lib/ace/assign/molecules/queue_scanner.rb
259
279
  - lib/ace/assign/molecules/skill_assign_source_resolver.rb
260
280
  - lib/ace/assign/molecules/step_renumberer.rb
261
281
  - lib/ace/assign/molecules/step_writer.rb
262
282
  - lib/ace/assign/organisms/assignment_executor.rb
283
+ - lib/ace/assign/organisms/task_assignment_creator.rb
263
284
  - lib/ace/assign/version.rb
264
285
  homepage: https://github.com/cs3b/ace
265
286
  licenses:
266
287
  - MIT
267
288
  metadata:
268
289
  homepage_uri: https://github.com/cs3b/ace
269
- source_code_uri: https://github.com/cs3b/ace
290
+ source_code_uri: https://github.com/cs3b/ace/tree/main/ace-assign/
270
291
  changelog_uri: https://github.com/cs3b/ace/blob/main/ace-assign/CHANGELOG.md
271
292
  rdoc_options: []
272
293
  require_paths: