ace-overseer 0.11.0 → 0.13.3

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: 94a8d4155aa2c5385f768be8044cec7375bf45be9f3e4c710635600426482e4b
4
- data.tar.gz: 55918116f099345f95b94156da6ca9be5afbd65d94b34116ccaa39dbaeddf9e7
3
+ metadata.gz: 88de815647c2cab7c69771f682d046320853108aaf88e2ce441bd6e82cf73de1
4
+ data.tar.gz: cf5dd24e95177473c95906caaf9eb0236e9e40f2c5d53b236097aa40a20a2dfc
5
5
  SHA512:
6
- metadata.gz: 10974ac46524c8555ce64a47867ba1a0bee986b6b63114ae7dde29202ef190bcadfb3c57310a1ada3631b4476e5b7329361b2660c09b0d4fcc1047bdbaaded55
7
- data.tar.gz: 5f25ff7e081379033014d606eae9f28f611ae35e01b4be77dff516ecccfbb6c2916027453ee07e31a30234114ea0ef53454ee833c3308ce6f373913c2240c8c5
6
+ metadata.gz: 9596ef81f280c9c834070a112cdd58055f963bcec89b6dc569cc0ee61ab5cf48ef824e6b1fd7caed11247bb4302b9fb956c47a60138722a89d3e7ee17a20503a
7
+ data.tar.gz: 2ab4047d48a95c905e0331b9b753360e4215ad4d1c2691f3906a3e8a464ae7bfeb671539efd264cadead2e94c3d1b810130f749a2324a7c037489f2873147534
data/CHANGELOG.md CHANGED
@@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.3] - 2026-03-28
11
+
12
+ ### Fixed
13
+ - Added an explicit git-repository guard for operational commands so `work-on`, `status`, and `prune` fail cleanly outside a repo or worktree instead of leaking raw `git` errors.
14
+
15
+ ### Technical
16
+ - Added command coverage for non-repo failure paths and updated launcher fixtures to initialize real git repos in temporary worktree test directories.
17
+
18
+ ## [0.13.2] - 2026-03-27
19
+
20
+ ### Fixed
21
+ - Preserved taskref-only preset compatibility by collapsing launcher task refs to the primary task when a preset does not declare `taskrefs`.
22
+
23
+ ## [0.13.1] - 2026-03-27
24
+
25
+ ### Fixed
26
+ - Updated work-on preset guard checks to evaluate active resolved refs after terminal filtering, preventing false single-task preset rejections on mixed terminal/non-terminal inputs.
27
+ - Preserved parent refs during taskref expansion when all subtasks are terminal, preventing empty expansion results from valid parent-task inputs.
28
+
29
+ ## [0.13.0] - 2026-03-27
30
+
31
+ ### Changed
32
+ - Routed `ace-overseer work-on` assignment launch through the shared `ace-assign create --task ...` creation path while preserving the existing CLI and pre-side-effect validation behavior.
33
+ - Updated package docs to describe the shared task-driven assignment creation flow with `ace-assign`.
34
+
35
+ ### Technical
36
+ - Added launcher and orchestrator coverage for the shared creator path and explicit task fixture resolution.
37
+
38
+ ## [0.12.1] - 2026-03-26
39
+
40
+ ### Fixed
41
+ - Tightened `ace-assign` gemspec constraint from `~> 0.16` to `~> 0.38` to match the runtime dependency introduced in v0.12.0.
42
+
43
+ ## [0.12.0] - 2026-03-26
44
+
45
+ ### Added
46
+ - Terminal task filtering in `WorkOnOrchestrator`: tasks with `done`, `skipped`, or `cancelled` status are automatically excluded before assignment creation, with user-visible skip reporting via progress callbacks.
47
+
48
+ ### Changed
49
+ - `resolve_requested_refs` now returns skipped terminal refs alongside resolved refs, raising an error when all requested refs are terminal.
50
+ - `expand_task_refs_in_order` and `extract_subtask_refs` filter out terminal subtasks during parent-to-subtask expansion.
51
+
52
+ ### Technical
53
+ - Added 4 tests covering single-terminal rejection, all-terminal rejection, mixed-set filtering, and subtask-level terminal filtering.
54
+
10
55
  ## [0.11.0] - 2026-03-23
11
56
 
12
57
  ### Changed
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  One command to provision a worktree, open a tmux window, and start working on a task.
5
5
 
6
- <img src="https://raw.githubusercontent.com/cs3b/ace/main/docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
6
+ <img src="../docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
7
7
  <br><br>
8
8
 
9
9
  <a href="https://rubygems.org/gems/ace-overseer"><img alt="Gem Version" src="https://img.shields.io/gem/v/ace-overseer.svg" /></a>
@@ -23,7 +23,7 @@ Starting task work means creating a worktree, opening a tmux window, and prepari
23
23
  ## How It Works
24
24
 
25
25
  1. Resolve task refs and create a scoped worktree.
26
- 2. Expand the assignment preset into concrete steps under `.ace-local/assign/`, then open a dedicated tmux window mapped to that worktree.
26
+ 2. Route assignment creation through `ace-assign create --task ...`, which expands the assignment preset into concrete steps under `.ace-local/assign/`, then open a dedicated tmux window mapped to that worktree.
27
27
  3. Instruct the agent inside the tmux window to act as an orchestrator and drive the assignment step-by-step.
28
28
 
29
29
  ## Use Cases
data/docs/usage.md CHANGED
@@ -29,6 +29,8 @@ Options:
29
29
  - `--debug`, `-d`: show debug output
30
30
  - `--help`, `-h`: show help
31
31
 
32
+ Internally, `work-on` now routes assignment creation through `ace-assign create --task ...`, so direct `ace-assign` and `ace-overseer` task flows use the same preset expansion behavior.
33
+
32
34
  ## `ace-overseer status`
33
35
 
34
36
  Show status for active task worktrees.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Overseer
5
+ module Atoms
6
+ module RepoGuard
7
+ MESSAGE = "ace-overseer must be run inside a git repository or worktree. Change into your project repo and retry."
8
+
9
+ def self.ensure_repo!(cwd: Dir.pwd)
10
+ return true if inside_repo?(cwd: cwd)
11
+
12
+ raise Ace::Support::Cli::Error.new(MESSAGE)
13
+ end
14
+
15
+ def self.inside_repo?(cwd: Dir.pwd)
16
+ system(
17
+ "git", "-C", cwd.to_s, "rev-parse", "--is-inside-work-tree",
18
+ out: File::NULL, err: File::NULL
19
+ )
20
+ rescue SystemCallError
21
+ false
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -26,6 +26,8 @@ module Ace
26
26
  end
27
27
 
28
28
  def call(**options)
29
+ Atoms::RepoGuard.ensure_repo!
30
+
29
31
  targets = Array(options[:targets] || [])
30
32
  progress = options[:quiet] ? nil : ->(msg) { puts msg }
31
33
  result = @orchestrator.call(
@@ -23,6 +23,7 @@ module Ace
23
23
  end
24
24
 
25
25
  def call(format:, **options)
26
+ Atoms::RepoGuard.ensure_repo!
26
27
  return if options[:quiet]
27
28
 
28
29
  if options[:watch] && format != "json"
@@ -21,6 +21,8 @@ module Ace
21
21
  end
22
22
 
23
23
  def call(task: nil, preset: nil, **options)
24
+ Atoms::RepoGuard.ensure_repo!
25
+
24
26
  task_refs = normalize_task_refs(task)
25
27
  if task_refs.empty?
26
28
  raise Ace::Support::Cli::Error.new("--task is required. Usage: ace-overseer work-on --task <ref>")
@@ -1,122 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
- require "fileutils"
5
- require "tempfile"
6
3
  require "ace/support/fs"
7
4
 
8
5
  module Ace
9
6
  module Overseer
10
7
  module Molecules
11
8
  class AssignmentLauncher
12
- def initialize(assignment_executor: nil)
9
+ def initialize(assignment_executor: nil, task_manager: nil)
13
10
  @assignment_executor = assignment_executor
11
+ @task_manager = task_manager
14
12
  end
15
13
 
16
14
  def launch(worktree_path:, preset_name:, task_ref:, subtask_refs: nil, task_refs: nil)
17
15
  with_worktree_context(worktree_path) do
18
- preset = load_preset!(preset_name)
19
- params = build_parameters(preset, task_ref, subtask_refs, task_refs)
20
- validate_parameters!(preset, params)
21
-
22
- steps = Ace::Assign::Atoms::PresetExpander.expand(preset, params)
23
- session_name = "#{preset_name}-#{task_ref}"
24
- job_path = write_job_file(session_name: session_name, description: preset["description"], steps: steps)
25
-
26
- executor = @assignment_executor || Ace::Assign::Organisms::AssignmentExecutor.new
27
- result = executor.start(job_path)
16
+ requested_refs = effective_task_refs(task_ref, subtask_refs, task_refs)
17
+ requested_refs = [task_ref] unless preset_supports_taskrefs?(preset_name: preset_name)
18
+
19
+ creator = Ace::Assign::Organisms::TaskAssignmentCreator.new(
20
+ task_manager: @task_manager,
21
+ executor: @assignment_executor
22
+ )
23
+ result = creator.call(
24
+ task_refs: requested_refs,
25
+ preset_name: preset_name,
26
+ primary_task_ref: task_ref
27
+ )
28
28
  current = result[:current]
29
29
 
30
30
  {
31
31
  assignment_id: result[:assignment].id,
32
32
  first_step: current ? "#{current.number}-#{current.name}" : nil,
33
- job_path: job_path
33
+ job_path: result[:job_path]
34
34
  }
35
35
  end
36
+ rescue Ace::Support::Cli::Error => e
37
+ raise Error, e.message
36
38
  end
37
39
 
38
40
  def preset_supports_taskrefs?(preset_name:, worktree_path: nil)
39
41
  if worktree_path
40
42
  with_worktree_context(worktree_path) do
41
- parameter_names(load_preset!(preset_name)).include?("taskrefs")
43
+ parameter_names(Ace::Assign::Atoms::PresetLoader.load(preset_name)).include?("taskrefs")
42
44
  end
43
45
  else
44
- parameter_names(load_preset!(preset_name)).include?("taskrefs")
46
+ parameter_names(Ace::Assign::Atoms::PresetLoader.load(preset_name)).include?("taskrefs")
45
47
  end
48
+ rescue Ace::Support::Cli::Error => e
49
+ raise Error, e.message
46
50
  end
47
51
 
48
52
  private
49
53
 
50
- def build_parameters(preset, task_ref, subtask_refs, task_refs)
51
- param_defs = preset["parameters"] || {}
52
- params = {}
53
- params["taskref"] = task_ref if param_defs.key?("taskref")
54
- if param_defs.key?("taskrefs")
55
- params["taskrefs"] = if task_refs&.any?
56
- task_refs
57
- elsif subtask_refs&.any?
58
- subtask_refs
59
- else
60
- [task_ref]
61
- end
62
- end
63
- params
64
- end
65
-
66
54
  def parameter_names(preset)
67
55
  (preset["parameters"] || {}).keys
68
56
  end
69
57
 
70
- def validate_parameters!(preset, params)
71
- errors = Ace::Assign::Atoms::PresetExpander.validate_parameters(preset, params)
72
- return if errors.empty?
73
-
74
- raise Error, errors.join(", ")
75
- end
76
-
77
- def write_job_file(session_name:, description:, steps:)
78
- job = {
79
- "session" => {
80
- "name" => session_name,
81
- "description" => description || "Prepared by ace-overseer"
82
- },
83
- "steps" => steps
84
- }
85
-
86
- dir = File.join(Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current, ".ace-local", "overseer")
87
- FileUtils.mkdir_p(dir)
88
- path = File.join(dir, "#{session_name}-job.yml")
89
- payload = YAML.dump(job)
90
- Tempfile.create(["#{session_name}-job", ".yml"], dir) do |tmp|
91
- tmp.write(payload)
92
- tmp.flush
93
- tmp.fsync
94
- FileUtils.mv(tmp.path, path)
95
- end
96
- path
97
- end
98
-
99
- def load_preset!(preset_name)
100
- path = preset_paths(preset_name).find { |candidate| File.exist?(candidate) }
101
- raise Error, "Assignment preset not found: #{preset_name}" unless path
102
-
103
- YAML.safe_load_file(path)
104
- end
105
-
106
- def preset_paths(preset_name)
107
- filename = "#{preset_name}.yml"
108
- paths = [
109
- File.join(Dir.pwd, ".ace", "assign", "presets", filename),
110
- File.join(Dir.pwd, "ace-assign", ".ace-defaults", "assign", "presets", filename),
111
- File.join(Dir.pwd, ".ace-defaults", "assign", "presets", filename)
112
- ]
113
-
114
- gem_root = Gem.loaded_specs["ace-assign"]&.gem_dir
115
- if gem_root
116
- paths << File.join(gem_root, ".ace-defaults", "assign", "presets", filename)
117
- end
58
+ def effective_task_refs(task_ref, subtask_refs, task_refs)
59
+ return task_refs if task_refs&.any?
60
+ return subtask_refs if subtask_refs&.any?
118
61
 
119
- paths.uniq
62
+ [task_ref]
120
63
  end
121
64
 
122
65
  def with_worktree_context(worktree_path)
@@ -24,8 +24,17 @@ module Ace
24
24
  raise Error, "No valid task references provided" if requested_refs.empty?
25
25
 
26
26
  progress.call("Loading task #{requested_refs.join(", ")}...")
27
- resolved_refs = resolve_requested_refs(requested_refs)
28
- primary_ref = requested_refs.first
27
+ resolved_refs, skipped_terminal = resolve_requested_refs(requested_refs)
28
+
29
+ if resolved_refs.empty?
30
+ raise Error, "All requested tasks are already terminal (done/skipped/cancelled): #{skipped_terminal.join(", ")}. No assignment created."
31
+ end
32
+
33
+ if skipped_terminal.any?
34
+ progress.call("Skipped terminal tasks (done/skipped/cancelled): #{skipped_terminal.join(", ")}")
35
+ end
36
+
37
+ primary_ref = resolved_refs.first[:ref]
29
38
  primary_task = resolved_refs.first[:task]
30
39
 
31
40
  preset_name = Atoms::PresetResolver.resolve(
@@ -33,7 +42,7 @@ module Ace
33
42
  cli_preset: cli_preset,
34
43
  default: @config["default_assign_preset"] || "work-on-task"
35
44
  )
36
- guard_multi_task_preset!(preset_name, requested_refs.length)
45
+ guard_multi_task_preset!(preset_name, resolved_refs.length)
37
46
 
38
47
  expanded_taskrefs = expand_task_refs_in_order(resolved_refs)
39
48
  primary_subtask_refs = extract_subtask_refs(primary_task)
@@ -97,7 +106,8 @@ module Ace
97
106
  subtasks = task.respond_to?(:subtasks) ? task.subtasks : nil
98
107
  return nil unless subtasks&.any?
99
108
 
100
- subtasks.map(&:id)
109
+ active = subtasks.reject { |st| Ace::Task::Atoms::TaskValidationRules.terminal_status?(st.status.to_s) }
110
+ active.any? ? active.map(&:id) : nil
101
111
  end
102
112
 
103
113
  def normalize_requested_refs(task_ref, task_refs)
@@ -109,7 +119,10 @@ module Ace
109
119
  end
110
120
 
111
121
  def resolve_requested_refs(requested_refs)
112
- requested_refs.map do |ref|
122
+ resolved = []
123
+ skipped_terminal = []
124
+
125
+ requested_refs.each do |ref|
113
126
  is_subtask = subtask_ref?(ref)
114
127
  task = @task_manager.show(ref.to_s)
115
128
  raise Error, "Task not found: #{ref}" unless task
@@ -120,8 +133,15 @@ module Ace
120
133
  "(or ace-bundle wfi://task/review), then retry."
121
134
  end
122
135
 
123
- {ref: ref.to_s, task: task, is_subtask: is_subtask}
136
+ if Ace::Task::Atoms::TaskValidationRules.terminal_status?(task.status.to_s)
137
+ skipped_terminal << ref.to_s
138
+ next
139
+ end
140
+
141
+ resolved << {ref: ref.to_s, task: task, is_subtask: is_subtask}
124
142
  end
143
+
144
+ [resolved, skipped_terminal]
125
145
  end
126
146
 
127
147
  def subtask_ref?(ref)
@@ -136,7 +156,10 @@ module Ace
136
156
  if entry[:is_subtask]
137
157
  [ref]
138
158
  elsif task.respond_to?(:subtasks) && task.subtasks&.any?
139
- task.subtasks.map(&:id)
159
+ active_subtasks = task.subtasks
160
+ .reject { |st| Ace::Task::Atoms::TaskValidationRules.terminal_status?(st.status.to_s) }
161
+ .map(&:id)
162
+ active_subtasks.empty? ? [ref] : active_subtasks
140
163
  else
141
164
  [ref]
142
165
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Overseer
5
- VERSION = "0.11.0"
5
+ VERSION = "0.13.3"
6
6
  end
7
7
  end
data/lib/ace/overseer.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "overseer/version"
12
12
  require_relative "overseer/models/work_context"
13
13
  require_relative "overseer/models/prune_candidate"
14
14
  require_relative "overseer/models/assignment_prune_candidate"
15
+ require_relative "overseer/atoms/repo_guard"
15
16
  require_relative "overseer/atoms/preset_resolver"
16
17
  require_relative "overseer/atoms/status_formatter"
17
18
  require_relative "overseer/molecules/worktree_provisioner"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-overseer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.16'
74
+ version: '0.40'
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.16'
81
+ version: '0.40'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ace-git
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -127,14 +127,14 @@ dependencies:
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '0.10'
130
+ version: '0.11'
131
131
  type: :runtime
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '0.10'
137
+ version: '0.11'
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: ace-support-test-helpers
140
140
  requirement: !ruby/object:Gem::Requirement
@@ -217,6 +217,7 @@ files:
217
217
  - handbook/workflow-instructions/overseer.wf.md
218
218
  - lib/ace/overseer.rb
219
219
  - lib/ace/overseer/atoms/preset_resolver.rb
220
+ - lib/ace/overseer/atoms/repo_guard.rb
220
221
  - lib/ace/overseer/atoms/status_formatter.rb
221
222
  - lib/ace/overseer/cli.rb
222
223
  - lib/ace/overseer/cli/commands/prune.rb