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 +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +2 -2
- data/docs/usage.md +2 -0
- data/lib/ace/overseer/atoms/repo_guard.rb +26 -0
- data/lib/ace/overseer/cli/commands/prune.rb +2 -0
- data/lib/ace/overseer/cli/commands/status.rb +1 -0
- data/lib/ace/overseer/cli/commands/work_on.rb +2 -0
- data/lib/ace/overseer/molecules/assignment_launcher.rb +25 -82
- data/lib/ace/overseer/organisms/work_on_orchestrator.rb +30 -7
- data/lib/ace/overseer/version.rb +1 -1
- data/lib/ace/overseer.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 88de815647c2cab7c69771f682d046320853108aaf88e2ce441bd6e82cf73de1
|
|
4
|
+
data.tar.gz: cf5dd24e95177473c95906caaf9eb0236e9e40f2c5d53b236097aa40a20a2dfc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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="
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
43
|
+
parameter_names(Ace::Assign::Atoms::PresetLoader.load(preset_name)).include?("taskrefs")
|
|
42
44
|
end
|
|
43
45
|
else
|
|
44
|
-
parameter_names(
|
|
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
|
|
71
|
-
|
|
72
|
-
return if
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
data/lib/ace/overseer/version.rb
CHANGED
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|