ace-overseer 0.18.2 → 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 +4 -4
- data/.ace-defaults/overseer/config.yml +2 -1
- data/CHANGELOG.md +7 -0
- data/README.md +3 -3
- data/docs/usage.md +18 -0
- data/lib/ace/overseer/atoms/runtime_setting.rb +21 -0
- data/lib/ace/overseer/molecules/window_opener.rb +43 -0
- data/lib/ace/overseer/organisms/prune_orchestrator.rb +16 -11
- data/lib/ace/overseer/organisms/work_on_orchestrator.rb +6 -6
- data/lib/ace/overseer/version.rb +1 -1
- data/lib/ace/overseer.rb +3 -2
- metadata +22 -7
- data/lib/ace/overseer/molecules/tmux_window_opener.rb +0 -52
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac2050e4e153d6bed54441f8b9a64aaea6a6bc4bcefc06413e4b7187487a20dc
|
|
4
|
+
data.tar.gz: e430481b7d4824d5359f7e61f3d575c3e96311af90f3f07f79eacf6952a871da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3106447b37e8b3e4faa21799da47441d1ce2b327231243148830480f507c490c86ec602b0fba2e8b1a5c1d7d584e8c7c92e26ce038b7a7a050e5cd6adf0c7801
|
|
7
|
+
data.tar.gz: e27c30a6e6e2cc318c05a348b6e01acbe4f6c5c692784512f1531830725edcd6210f65db5f9430f95f9fe0a9debe91732579534b091f4fc3d33f0230ed7911b6
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.19.0] - 2026-10-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Work-on and prune manage worktree windows through the `ace-runtime` contract (`TmuxWindowOpener` replaced by `WindowOpener`); the terminal runtime comes from the new `runtime:` config key (`tmux` | `herdr` | `auto`; `ACE_RUNTIME` env wins, detection as fallback, explicit fail when nothing is live).
|
|
14
|
+
- Config key `tmux_window_presets` renamed to `window_presets` (no legacy alias, ADR-024); "tmux window" progress text is runtime-neutral.
|
|
15
|
+
- New dependency: `ace-runtime` (~> 0.1); `ace-tmux` is kept to install the tmux adapter.
|
|
16
|
+
|
|
10
17
|
## [0.18.2] - 2026-10-02
|
|
11
18
|
|
|
12
19
|
### Technical
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|

|
|
20
20
|
|
|
21
|
-
Starting task work means creating a worktree, opening a
|
|
21
|
+
Starting task work means creating a worktree, opening a terminal window, and preparing an assignment - three manual steps before you even begin coding. ace-overseer collapses that into a single command, tracks what is running where, and cleans up finished worktrees so nothing lingers. You can jump straight to a focused worktree any time with a single invocation.
|
|
22
22
|
|
|
23
23
|
On a configured Lab machine, the same CLI can prepare and dispatch the Lab's
|
|
24
24
|
system-owned `Work` objects. The default remains the existing tmux runtime;
|
|
@@ -27,8 +27,8 @@ using Lab always requires an explicit `--runtime lab` or a Lab-only command.
|
|
|
27
27
|
## How It Works
|
|
28
28
|
|
|
29
29
|
1. Resolve task refs and create a scoped worktree.
|
|
30
|
-
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
|
|
31
|
-
3. Instruct the agent inside the
|
|
30
|
+
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 terminal window mapped to that worktree.
|
|
31
|
+
3. Instruct the agent inside the terminal window to act as an orchestrator and drive the assignment step-by-step.
|
|
32
32
|
|
|
33
33
|
## Use Cases
|
|
34
34
|
|
data/docs/usage.md
CHANGED
|
@@ -102,6 +102,24 @@ Check dashboard: `ace-overseer status`.
|
|
|
102
102
|
|
|
103
103
|
Preview then prune: `ace-overseer prune --dry-run`, then `ace-overseer prune --yes`.
|
|
104
104
|
|
|
105
|
+
## Terminal Runtime Configuration
|
|
106
|
+
|
|
107
|
+
Work-on opens the task's terminal window and prune closes it through the
|
|
108
|
+
runtime-neutral `ace-runtime` contract. Select the terminal runtime in
|
|
109
|
+
`.ace/overseer/config.yml`:
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
# tmux | herdr | auto (default) — auto detects tmux first, then herdr;
|
|
113
|
+
# ACE_RUNTIME env wins over this key; nothing detected fails explicitly
|
|
114
|
+
runtime: auto
|
|
115
|
+
window_presets:
|
|
116
|
+
"work-on-task": "work-on-task" # preset applied to the opened window/tab
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The preset key is `window_presets` (renamed from `tmux_window_presets`
|
|
120
|
+
pre-1.0; there is no legacy alias). The same configured runtime closes a
|
|
121
|
+
worktree's window during accepted prunes.
|
|
122
|
+
|
|
105
123
|
Prune migrated work with a declared destination:
|
|
106
124
|
|
|
107
125
|
```bash
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Overseer
|
|
5
|
+
module Atoms
|
|
6
|
+
# Normalizes the overseer `runtime:` config setting into a contract
|
|
7
|
+
# runtime name. The shipped default is `auto`, which is an
|
|
8
|
+
# overseer-level instruction to detect, never a contract runtime
|
|
9
|
+
# name; it normalizes to nil so the selector falls through to
|
|
10
|
+
# ACE_RUNTIME / detection.
|
|
11
|
+
module RuntimeSetting
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def normalize(value)
|
|
15
|
+
normalized = value.to_s.strip
|
|
16
|
+
normalized.empty? || normalized == "auto" ? nil : normalized
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ace/runtime"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Overseer
|
|
7
|
+
module Molecules
|
|
8
|
+
# Opens the task's terminal window through the runtime contract.
|
|
9
|
+
# The window is named after the worktree basename under the shared
|
|
10
|
+
# naming policy and rooted at the worktree path.
|
|
11
|
+
class WindowOpener
|
|
12
|
+
def initialize(runtime: nil, config: nil, env: ENV)
|
|
13
|
+
@runtime = runtime
|
|
14
|
+
@config = config || Ace::Overseer.config
|
|
15
|
+
@env = env
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def open(worktree_path:, preset: nil)
|
|
19
|
+
name = Ace::Runtime.sanitize_name(File.basename(worktree_path.to_s))
|
|
20
|
+
adapter.ensure_window(
|
|
21
|
+
name: name,
|
|
22
|
+
root: File.expand_path(worktree_path.to_s),
|
|
23
|
+
preset: preset
|
|
24
|
+
)
|
|
25
|
+
name
|
|
26
|
+
rescue Ace::Runtime::Error => e
|
|
27
|
+
raise Error, "Failed to open terminal window for #{worktree_path}: #{e.message}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :config, :env
|
|
33
|
+
|
|
34
|
+
def adapter
|
|
35
|
+
@adapter ||= @runtime || Ace::Runtime::Molecules::RuntimeSelector.new(
|
|
36
|
+
config: {runtime: Atoms::RuntimeSetting.normalize(config["runtime"])},
|
|
37
|
+
env: env
|
|
38
|
+
).resolve
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -19,12 +19,12 @@ module Ace
|
|
|
19
19
|
# revalidation blocks; it only suppresses the interactive confirmation
|
|
20
20
|
# for already-safe candidates.
|
|
21
21
|
class PruneOrchestrator
|
|
22
|
-
def initialize(worktree_manager: nil, prune_checker: nil,
|
|
22
|
+
def initialize(worktree_manager: nil, prune_checker: nil, runtime: nil, config: nil,
|
|
23
23
|
assignment_prune_checker: nil, assignment_manager: nil, lifecycle_exclusion: nil,
|
|
24
24
|
preservation_manifest_loader: nil)
|
|
25
25
|
@worktree_manager = worktree_manager || Ace::Git::Worktree::Organisms::WorktreeManager.new
|
|
26
26
|
@prune_checker = prune_checker || Molecules::PruneSafetyChecker.new
|
|
27
|
-
@
|
|
27
|
+
@runtime = runtime
|
|
28
28
|
@config = config || Ace::Overseer.config
|
|
29
29
|
@assignment_prune_checker = assignment_prune_checker || Molecules::AssignmentPruneSafetyChecker.new
|
|
30
30
|
@assignment_manager = assignment_manager || Ace::Assign::Molecules::AssignmentManager.new
|
|
@@ -168,7 +168,7 @@ module Ace
|
|
|
168
168
|
)
|
|
169
169
|
return remove_result unless remove_result[:success]
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
close_runtime_window(candidate.worktree_path)
|
|
172
172
|
# Only the branch the recheck verified is deletable — never a
|
|
173
173
|
# freshly observed branch name.
|
|
174
174
|
branch_result = delete_branch(repo, branch: verified_branch, head: verified_head)
|
|
@@ -385,16 +385,21 @@ module Ace
|
|
|
385
385
|
progress.call("Warning: failed to prune stale worktree metadata: #{e.message}")
|
|
386
386
|
end
|
|
387
387
|
|
|
388
|
-
def
|
|
389
|
-
window_name = File.basename(worktree_path)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
rescue
|
|
388
|
+
def close_runtime_window(worktree_path)
|
|
389
|
+
window_name = Ace::Runtime.sanitize_name(File.basename(worktree_path.to_s))
|
|
390
|
+
runtime.close_window(window: window_name)
|
|
391
|
+
true
|
|
392
|
+
rescue StandardError
|
|
393
|
+
# Terminal cleanup must never fail the prune; a window that
|
|
394
|
+
# could not be closed is left in place.
|
|
396
395
|
false
|
|
397
396
|
end
|
|
397
|
+
|
|
398
|
+
def runtime
|
|
399
|
+
@runtime ||= Ace::Runtime::Molecules::RuntimeSelector.new(
|
|
400
|
+
config: {runtime: Atoms::RuntimeSetting.normalize(@config["runtime"])}
|
|
401
|
+
).resolve
|
|
402
|
+
end
|
|
398
403
|
end
|
|
399
404
|
end
|
|
400
405
|
end
|
|
@@ -7,10 +7,10 @@ module Ace
|
|
|
7
7
|
# B36TS subtask pattern: "8pp.t.q7w.a" (parent 9-char ID + dot + single char)
|
|
8
8
|
SUBTASK_PATTERN = /^[0-9a-z]{3}\.[a-z]\.[0-9a-z]{3}\.[a-z0-9]$/
|
|
9
9
|
|
|
10
|
-
def initialize(worktree_provisioner: nil,
|
|
10
|
+
def initialize(worktree_provisioner: nil, window_opener: nil, assignment_launcher: nil,
|
|
11
11
|
task_loader: nil, config: nil, assignment_detector: nil, lifecycle_exclusion: nil)
|
|
12
12
|
@worktree_provisioner = worktree_provisioner || Molecules::WorktreeProvisioner.new
|
|
13
|
-
@
|
|
13
|
+
@window_opener = window_opener || Molecules::WindowOpener.new
|
|
14
14
|
@task_manager = task_loader || Ace::Task::Organisms::TaskManager.new
|
|
15
15
|
@assignment_launcher = assignment_launcher || Molecules::AssignmentLauncher.new(task_manager: @task_manager)
|
|
16
16
|
@config = config || Ace::Overseer.config
|
|
@@ -47,7 +47,7 @@ module Ace
|
|
|
47
47
|
|
|
48
48
|
expanded_taskrefs = expand_task_refs_in_order(resolved_refs)
|
|
49
49
|
primary_subtask_refs = extract_subtask_refs(primary_task)
|
|
50
|
-
|
|
50
|
+
window_preset = @config.dig("window_presets", preset_name)
|
|
51
51
|
|
|
52
52
|
# Participate in prune exclusion for the whole start: provisioning,
|
|
53
53
|
# runtime window and assignment launch all hold the shared side of
|
|
@@ -67,10 +67,10 @@ module Ace
|
|
|
67
67
|
progress.call("Worktree exists at #{worktree[:worktree_path]}")
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
progress.call("Opening
|
|
71
|
-
@
|
|
70
|
+
progress.call("Opening terminal window...")
|
|
71
|
+
@window_opener.open(
|
|
72
72
|
worktree_path: worktree[:worktree_path],
|
|
73
|
-
preset:
|
|
73
|
+
preset: window_preset
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
progress.call("Checking assignment status...")
|
data/lib/ace/overseer/version.rb
CHANGED
data/lib/ace/overseer.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "ace/assign"
|
|
|
6
6
|
require "ace/git"
|
|
7
7
|
require "ace/git/worktree"
|
|
8
8
|
require "ace/task"
|
|
9
|
-
require "ace/
|
|
9
|
+
require "ace/runtime"
|
|
10
10
|
|
|
11
11
|
module Ace
|
|
12
12
|
module Overseer
|
|
@@ -21,10 +21,11 @@ require_relative "overseer/models/assignment_prune_candidate"
|
|
|
21
21
|
require_relative "overseer/models/preservation_proof"
|
|
22
22
|
require_relative "overseer/atoms/repo_guard"
|
|
23
23
|
require_relative "overseer/atoms/preset_resolver"
|
|
24
|
+
require_relative "overseer/atoms/runtime_setting"
|
|
24
25
|
require_relative "overseer/atoms/status_formatter"
|
|
25
26
|
require_relative "overseer/molecules/worktree_provisioner"
|
|
26
27
|
require_relative "overseer/molecules/lab_client"
|
|
27
|
-
require_relative "overseer/molecules/
|
|
28
|
+
require_relative "overseer/molecules/window_opener"
|
|
28
29
|
require_relative "overseer/molecules/assignment_launcher"
|
|
29
30
|
require_relative "overseer/molecules/worktree_context_collector"
|
|
30
31
|
require_relative "overseer/molecules/preservation_manifest"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-overseer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-04 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -71,14 +71,14 @@ dependencies:
|
|
|
71
71
|
requirements:
|
|
72
72
|
- - "~>"
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0.
|
|
74
|
+
version: '0.62'
|
|
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.62'
|
|
82
82
|
- !ruby/object:Gem::Dependency
|
|
83
83
|
name: ace-git
|
|
84
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -99,14 +99,14 @@ dependencies:
|
|
|
99
99
|
requirements:
|
|
100
100
|
- - "~>"
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0.
|
|
102
|
+
version: '0.25'
|
|
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.25'
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: ace-task
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -121,6 +121,20 @@ dependencies:
|
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '0.37'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: ace-runtime
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0.1'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0.1'
|
|
124
138
|
- !ruby/object:Gem::Dependency
|
|
125
139
|
name: ace-tmux
|
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -220,6 +234,7 @@ files:
|
|
|
220
234
|
- lib/ace/overseer.rb
|
|
221
235
|
- lib/ace/overseer/atoms/preset_resolver.rb
|
|
222
236
|
- lib/ace/overseer/atoms/repo_guard.rb
|
|
237
|
+
- lib/ace/overseer/atoms/runtime_setting.rb
|
|
223
238
|
- lib/ace/overseer/atoms/status_formatter.rb
|
|
224
239
|
- lib/ace/overseer/cli.rb
|
|
225
240
|
- lib/ace/overseer/cli/commands/agents.rb
|
|
@@ -242,7 +257,7 @@ files:
|
|
|
242
257
|
- lib/ace/overseer/molecules/lab_prune_safety_checker.rb
|
|
243
258
|
- lib/ace/overseer/molecules/preservation_manifest.rb
|
|
244
259
|
- lib/ace/overseer/molecules/prune_safety_checker.rb
|
|
245
|
-
- lib/ace/overseer/molecules/
|
|
260
|
+
- lib/ace/overseer/molecules/window_opener.rb
|
|
246
261
|
- lib/ace/overseer/molecules/worktree_context_collector.rb
|
|
247
262
|
- lib/ace/overseer/molecules/worktree_provisioner.rb
|
|
248
263
|
- lib/ace/overseer/organisms/prune_orchestrator.rb
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "ace/tmux"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Overseer
|
|
7
|
-
module Molecules
|
|
8
|
-
class TmuxWindowOpener
|
|
9
|
-
def initialize(tmux_window_command: nil, tmux_executor: nil)
|
|
10
|
-
@tmux_window_command = tmux_window_command || Ace::Tmux::CLI::Commands::Window.new
|
|
11
|
-
@tmux_executor = tmux_executor || Ace::Tmux::Molecules::TmuxExecutor.new
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def open(worktree_path:, preset: nil)
|
|
15
|
-
ensure_session_exists
|
|
16
|
-
return if window_already_open?(worktree_path)
|
|
17
|
-
|
|
18
|
-
@tmux_window_command.call(
|
|
19
|
-
root: worktree_path.to_s,
|
|
20
|
-
preset: preset,
|
|
21
|
-
quiet: true,
|
|
22
|
-
session: ENV["ACE_TMUX_SESSION"]
|
|
23
|
-
)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def ensure_session_exists
|
|
29
|
-
session = ENV["ACE_TMUX_SESSION"]
|
|
30
|
-
return unless session && !session.empty?
|
|
31
|
-
return if @tmux_executor.capture(["tmux", "has-session", "-t", session]).success?
|
|
32
|
-
|
|
33
|
-
created = @tmux_executor.run(["tmux", "new-session", "-d", "-s", session])
|
|
34
|
-
raise "Failed to create tmux session '#{session}'" unless created
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def window_already_open?(worktree_path)
|
|
38
|
-
session = ENV["ACE_TMUX_SESSION"]
|
|
39
|
-
return false unless session
|
|
40
|
-
|
|
41
|
-
name = Ace::Tmux::Atoms::WindowNameSanitizer.call(File.basename(worktree_path.to_s))
|
|
42
|
-
result = @tmux_executor.capture(["tmux", "list-windows", "-t", session, "-F", '#{window_name}'])
|
|
43
|
-
return false unless result.success?
|
|
44
|
-
|
|
45
|
-
result.stdout.split("\n").any? { |w| w.strip == name }
|
|
46
|
-
rescue
|
|
47
|
-
false
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|