ace-git-worktree 0.24.0 → 0.25.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 440da5b336b58817b1e6bbc1b8d57de396e78848b9e9c289d1492ca85fc7065a
4
- data.tar.gz: 432c570346044a2351c72355b729501cef4723507fb64f0298307f7790edf289
3
+ metadata.gz: bead5eaf827aaa36f2c9cfcc51917d4462ddeef9d5df0320514d8392660bc118
4
+ data.tar.gz: 00fe7a68a1bb85d93d9a88d8d5ac5d89832d6a962b657e2e529f57d8ec375abe
5
5
  SHA512:
6
- metadata.gz: c4c1f7d225354653e5d6d4c3ff8e88c0c8b4cee1142707e5d51181569b961b58e81fd1f64bca355a78a6675205429654420ed9776481470204342f70e7bfc9a5
7
- data.tar.gz: 46e6f7112657db3d867a69db98dacbe429883672beb37b83fa35bda5b059d071a8a318fe5d26adc90835da05a3b4f74d105b096244be92beb61a904007652358
6
+ metadata.gz: 225bfd489abc65b33e94232f77f5d78f33403b87cc4a3980aa63481a875787ed169e5ce55d91c7ef4d886e29bd09aaf094204e4c655f903f36820a2a5777a020
7
+ data.tar.gz: a7e9e80a0668e0d959606e8a1e4f7bef334863bdc5a4f325272f29e2a42eb36256744dbe44ee7e6bbde61851dd478e528e24577ea1c384911407faa912fdb3d5
@@ -28,11 +28,12 @@ git:
28
28
  # If true, provides enhanced navigation experience after worktree creation
29
29
  auto_navigate: true
30
30
 
31
- # Launch tmux session after worktree creation
32
- # Requires ace-tmux to be installed. When enabled, replaces "cd ..." hint
33
- # with automatic tmux session launch rooted at the new worktree.
31
+ # Open a terminal window/tab after worktree creation
32
+ # Uses the configured terminal runtime (ace-runtime selection: explicit
33
+ # > ACE_RUNTIME > ace-runtime config > detection) and roots it at the
34
+ # new worktree. When enabled but no runtime is live, shows the cd hint.
34
35
  # Values: true | false
35
- tmux: false
36
+ terminal: false
36
37
 
37
38
  # Task-based naming and behavior configuration
38
39
  task:
data/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.25.0] - 2026-10-04
11
+
12
+ ### Added
13
+ - Post-create terminal opening goes through the `ace-runtime` contract: inside a live runtime a window/tab rooted at the worktree opens via `ensure_window`; with tmux explicitly selected outside any runtime, `ace-tmux start` bootstraps the session; explicit herdr never launches tmux; without a live runtime the cd hint is shown. Config key `tmux` renamed to `terminal` (no legacy alias, ADR-024).
14
+ - New dependencies: `ace-runtime` (~> 0.1) and `ace-tmux` (~> 0.18, tmux adapter).
15
+
16
+ ## [0.24.1] - 2026-09-29
17
+
18
+ ### Fixed
19
+ - Cleanup merge proof requires PR evidence provenance (server, repository,
20
+ branch, base) to agree with the resolved selection; incomplete or
21
+ conflicting provenance retains the candidate, so identical branch/PR
22
+ numbers in different repositories can never cross-prove removal.
23
+ - Cleanup targets resolve to the PR base branch across ref forms:
24
+ bare ("main"), remote-prefixed ("origin/main"), and fully qualified
25
+ ("refs/heads/main", "refs/remotes/origin/main").
26
+
10
27
  ## [0.24.0] - 2026-09-28
11
28
 
12
29
  ### Added
@@ -93,7 +93,7 @@ git:
93
93
  on_delete: true
94
94
  ```
95
95
 
96
- Add hooks, auto-push, tmux launch, or PR automation only after the basic create/switch/remove loop feels right.
96
+ Add hooks, auto-push, terminal launch, or PR automation only after the basic create/switch/remove loop feels right.
97
97
 
98
98
  ## Common Commands
99
99
 
data/docs/usage.md CHANGED
@@ -392,7 +392,15 @@ git:
392
392
  worktree:
393
393
  root_path: ".ace-wt"
394
394
  auto_navigate: true
395
- tmux: false
395
+ terminal: false
396
+ # terminal: true opens a window/tab rooted at the new worktree through
397
+ # the ace-runtime contract (renamed from `tmux`; boolean, no alias).
398
+ # Runtime selection follows ACE_RUNTIME > .ace/runtime/config.yml >
399
+ # detection (tmux first). Inside a live runtime the window/tab opens
400
+ # via the contract; with tmux explicitly selected but no live runtime,
401
+ # `ace-tmux start` bootstraps a session; anything else without a live
402
+ # runtime (including explicit herdr outside herdr) prints the cd hint
403
+ # instead — tmux is never launched unless tmux is the selection.
396
404
  task:
397
405
  directory_format: "task.{task_id}"
398
406
  branch_format: "{id}-{slug}"
@@ -459,7 +459,7 @@ module Ace
459
459
  provenance = {
460
460
  "root_path" => provenance_for.call("root_path"),
461
461
  "auto_navigate" => provenance_for.call("auto_navigate"),
462
- "tmux" => provenance_for.call("tmux"),
462
+ "terminal" => provenance_for.call("terminal"),
463
463
  "mise_trust_auto" => provenance_for.call("mise_trust_auto")
464
464
  }
465
465
 
@@ -678,10 +678,10 @@ module Ace
678
678
 
679
679
  display_warnings(result[:warnings]) if result[:warnings]
680
680
 
681
- # Display cd command or launch tmux
681
+ # Display cd command or open a terminal
682
682
  unless dry_run
683
683
  puts ""
684
- launch_tmux_or_display_cd(result[:worktree_path])
684
+ open_terminal_or_display_cd(result[:worktree_path])
685
685
  end
686
686
  end
687
687
 
@@ -722,9 +722,9 @@ module Ace
722
722
 
723
723
  display_warnings(result[:warnings]) if result[:warnings]
724
724
 
725
- # Display cd command or launch tmux
725
+ # Display cd command or open a terminal
726
726
  unless dry_run
727
- launch_tmux_or_display_cd(result[:worktree_path])
727
+ open_terminal_or_display_cd(result[:worktree_path])
728
728
  end
729
729
  end
730
730
 
@@ -770,9 +770,9 @@ module Ace
770
770
 
771
771
  display_warnings(result[:warnings]) if result[:warnings]
772
772
 
773
- # Display cd command or launch tmux
773
+ # Display cd command or open a terminal
774
774
  unless dry_run
775
- launch_tmux_or_display_cd(result[:worktree_path])
775
+ open_terminal_or_display_cd(result[:worktree_path])
776
776
  end
777
777
  end
778
778
 
@@ -795,7 +795,7 @@ module Ace
795
795
 
796
796
  display_warnings(result[:warnings]) if result[:warnings]
797
797
  puts ""
798
- launch_tmux_or_display_cd(result[:worktree_path])
798
+ open_terminal_or_display_cd(result[:worktree_path])
799
799
  end
800
800
  end
801
801
 
@@ -830,41 +830,74 @@ module Ace
830
830
  end
831
831
  end
832
832
 
833
- # Launch tmux session or display cd hint for navigation
833
+ # Open a terminal for the new worktree or display cd hint
834
834
  #
835
- # When tmux config is enabled and ace-tmux is available, launches a tmux
836
- # session rooted at the worktree path (replaces current process).
837
- # Uses `start` outside tmux and `window` inside tmux.
838
- # Otherwise falls back to displaying the cd command.
835
+ # When terminal config is enabled, opens a window/tab rooted at the
836
+ # worktree through the runtime contract inside a live runtime, and
837
+ # falls back to the tmux-native `ace-tmux start` when tmux is the
838
+ # explicit selection outside any live runtime (the contract has no
839
+ # session-create intent). An explicit herdr selection never
840
+ # launches tmux. Otherwise falls back to displaying the cd command.
839
841
  #
840
842
  # @param worktree_path [String] Path to the worktree
841
- def launch_tmux_or_display_cd(worktree_path)
843
+ def open_terminal_or_display_cd(worktree_path)
842
844
  return unless worktree_path
843
845
 
844
- if tmux_enabled?
845
- if ace_tmux_available?
846
- command = ENV["TMUX"].to_s.strip.empty? ? "start" : "window"
847
- Kernel.exec("ace-tmux", command, "--root", worktree_path)
848
- else
849
- puts "Warning: tmux is enabled in config but ace-tmux is not installed."
850
- puts "Install ace-tmux or disable tmux in .ace/git/worktree.yml"
846
+ unless terminal_enabled?
847
+ puts "cd #{worktree_path}"
848
+ return
849
+ end
850
+
851
+ detected = Ace::Runtime.detect
852
+ runtime_name = explicit_runtime_name || detected&.to_s
853
+
854
+ if runtime_name.nil?
855
+ puts "Warning: terminal is enabled but no terminal runtime is detected."
856
+ puts "Run inside tmux/herdr, set ACE_RUNTIME, or configure runtime under the ace-runtime config namespace."
857
+ puts "cd #{worktree_path}"
858
+ return
859
+ end
860
+
861
+ if detected && detected.to_s == runtime_name
862
+ name = Ace::Runtime.sanitize_name(File.basename(worktree_path))
863
+ Ace::Runtime.resolve(runtime_name).ensure_window(name: name, root: File.expand_path(worktree_path))
864
+ elsif runtime_name == "tmux"
865
+ unless ace_tmux_available?
866
+ puts "Warning: tmux terminal is enabled but ace-tmux is not installed."
867
+ puts "Install ace-tmux or disable terminal in .ace/git/worktree.yml"
851
868
  puts "cd #{worktree_path}"
869
+ return
852
870
  end
871
+ Kernel.exec("ace-tmux", "start", "--root", worktree_path)
853
872
  else
873
+ puts "Warning: the '#{runtime_name}' runtime is not live; cannot open a terminal at #{worktree_path}."
854
874
  puts "cd #{worktree_path}"
855
875
  end
876
+ rescue Ace::Runtime::Error => e
877
+ puts "Warning: failed to open a terminal window at #{worktree_path}: #{e.message}"
878
+ puts "cd #{worktree_path}"
879
+ end
880
+
881
+ # Explicit runtime selection: ACE_RUNTIME env, then the
882
+ # ace-runtime config namespace — the same precedence every
883
+ # consumer uses (k86.3). The value `auto` means "detect", never
884
+ # a runtime name, so it normalizes to unset.
885
+ def explicit_runtime_name
886
+ explicit = ENV["ACE_RUNTIME"].to_s.strip
887
+ explicit = Ace::Runtime.config["runtime"].to_s.strip if explicit.empty?
888
+ explicit.empty? || explicit == "auto" ? nil : explicit
856
889
  end
857
890
 
858
- # Check if tmux integration is enabled in config
891
+ # Check if terminal integration is enabled in config
859
892
  #
860
- # @return [Boolean] true if tmux is enabled
861
- def tmux_enabled?
893
+ # @return [Boolean] true if terminal launch is enabled
894
+ def terminal_enabled?
862
895
  require_relative "../molecules/config_loader"
863
896
  config_loader = Ace::Git::Worktree::Molecules::ConfigLoader.new
864
897
  config = config_loader.load
865
898
  return false unless config
866
899
 
867
- config.tmux?
900
+ config.terminal?
868
901
  rescue
869
902
  false
870
903
  end
@@ -29,7 +29,7 @@ module Ace
29
29
  DEFAULT_CONFIG = {
30
30
  "root_path" => ".ace-wt",
31
31
  "auto_navigate" => true,
32
- "tmux" => false,
32
+ "terminal" => false,
33
33
  "mise_trust_auto" => true,
34
34
  "task" => {
35
35
  "directory_format" => "t.{task_id}",
@@ -76,7 +76,7 @@ module Ace
76
76
  # Configuration namespace paths
77
77
  CONFIG_NAMESPACE = ["git", "worktree"].freeze
78
78
 
79
- attr_reader :root_path, :auto_navigate, :tmux, :mise_trust_auto, :task_config, :pr_config, :branch_config, :cleanup_config, :bootstrap_config, :hooks_config
79
+ attr_reader :root_path, :auto_navigate, :terminal, :mise_trust_auto, :task_config, :pr_config, :branch_config, :cleanup_config, :bootstrap_config, :hooks_config
80
80
 
81
81
  # Initialize a new WorktreeConfig
82
82
  #
@@ -111,11 +111,11 @@ module Ace
111
111
  @auto_navigate
112
112
  end
113
113
 
114
- # Check if tmux session should be launched after worktree creation
114
+ # Check if a terminal window should be opened after worktree creation
115
115
  #
116
- # @return [Boolean] true if tmux launch is enabled
117
- def tmux?
118
- @tmux
116
+ # @return [Boolean] true if terminal launch is enabled
117
+ def terminal?
118
+ @terminal
119
119
  end
120
120
 
121
121
  # Check if mise should automatically trust worktree directories
@@ -458,7 +458,7 @@ module Ace
458
458
  def initialize_attributes
459
459
  @root_path = @merged_config["root_path"]
460
460
  @auto_navigate = @merged_config["auto_navigate"]
461
- @tmux = @merged_config["tmux"]
461
+ @terminal = @merged_config["terminal"]
462
462
  @mise_trust_auto = @merged_config["mise_trust_auto"]
463
463
  @task_config = @merged_config["task"] || {}
464
464
  @pr_config = @merged_config["pr"] || {}
@@ -14,8 +14,11 @@ module Ace
14
14
  # Only confirmed remote merge evidence (:merged) can prove removal;
15
15
  # object states (:no_pr, :open, :closed_unmerged) and transport
16
16
  # failures (:offline, :authentication_error, :malformed) always
17
- # retain the candidate. Local ancestry proof is handled by the
18
- # reporter before this resolver is consulted.
17
+ # retain the candidate. Merge proof additionally requires the
18
+ # evidence's provenance (server, repository, branch, base) to agree
19
+ # with the resolved selection; incomplete or conflicting provenance
20
+ # retains. Local ancestry proof is handled by the reporter before
21
+ # this resolver is consulted.
19
22
  class CleanupPrResolver
20
23
  # @param target [String] base ref the PR must have targeted
21
24
  # @param target_sha [String] commit SHA of the target
@@ -57,7 +60,10 @@ module Ace
57
60
  return unproven_result(:no_pr) unless evidence
58
61
  return unproven_result(:open) if evidence.state == :open
59
62
  return unproven_result(:closed_unmerged) if evidence.state == :closed
60
- return unproven_result(:no_pr, "state_unavailable") unless evidence.state == :merged
63
+ return unproven_result(:merged, "state_unavailable") unless evidence.state == :merged
64
+
65
+ problem = provenance_problem(evidence, branch)
66
+ return unproven_result(:merged, problem) if problem
61
67
  return unproven_result(:merged, "merge_commit_unavailable") unless evidence.merge_commit_sha
62
68
  return unproven_result(:merged, "merge_commit_unreachable") unless ancestor?(evidence.merge_commit_sha, @target_sha)
63
69
 
@@ -118,6 +124,53 @@ module Ace
118
124
  end
119
125
  end
120
126
 
127
+ # Merge proof may only remove a candidate whose evidence provably
128
+ # belongs to the resolved server and the candidate's branch/base.
129
+ # PR and branch identities are not globally unique: incomplete
130
+ # provenance cannot be verified and conflicting provenance means
131
+ # the evidence was routed from another repository — both retain.
132
+ # @return [String, nil] retention reason, or nil when provenance agrees
133
+ def provenance_problem(evidence, branch)
134
+ server = resolved_server
135
+ unless server && evidence.server_name && evidence.base_repository_url &&
136
+ evidence.head_ref && evidence.base_ref
137
+ return "provenance_incomplete"
138
+ end
139
+
140
+ return "provenance_conflict" unless evidence.server_name == server.name
141
+ return "provenance_conflict" unless Ace::Git::Atoms::ServerUrl.match?(evidence.base_repository_url, server.url)
142
+ return "provenance_conflict" unless evidence.head_ref == branch
143
+ return "provenance_conflict" unless evidence.base_ref == target_branch
144
+
145
+ nil
146
+ end
147
+
148
+ # The branch the PR must have targeted. Targets arrive as bare
149
+ # branch names, remote-tracking refs ("origin/main",
150
+ # "refs/remotes/origin/main"), or local refs ("refs/heads/main");
151
+ # all of those forms name the same branch the PR base reports. A
152
+ # path segment only counts as a remote prefix when it names a
153
+ # configured git remote, so slashed branch names stay strict.
154
+ def target_branch
155
+ ref = @target.to_s
156
+ ref = ref.sub(%r{\Arefs/remotes/([^/]+)/}) do
157
+ git_remotes.include?(Regexp.last_match(1)) ? "" : Regexp.last_match(0)
158
+ end
159
+ ref = ref.sub(%r{\Arefs/heads/}, "")
160
+ if ref.include?("/")
161
+ prefix, rest = ref.split("/", 2)
162
+ ref = rest if git_remotes.include?(prefix)
163
+ end
164
+ ref
165
+ end
166
+
167
+ def git_remotes
168
+ out, _status = Open3.capture2("git", "remote")
169
+ out.to_s.lines.map(&:strip).reject(&:empty?)
170
+ rescue StandardError
171
+ []
172
+ end
173
+
121
174
  def proof_result(evidence, proof, candidate_sha, pr_head_sha, merge_commit_sha)
122
175
  {
123
176
  status: :merged,
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Git
5
5
  module Worktree
6
- VERSION = "0.24.0"
6
+ VERSION = "0.25.0"
7
7
  end
8
8
  end
9
9
  end
@@ -12,6 +12,7 @@ end
12
12
  # Core ace-git dependency for Git operations
13
13
  require "ace/git"
14
14
  require "ace/support/config"
15
+ require "ace/runtime"
15
16
 
16
17
  require_relative "worktree/version"
17
18
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-git-worktree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-29 00:00:00.000000000 Z
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
@@ -79,6 +79,34 @@ dependencies:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0.37'
82
+ - !ruby/object:Gem::Dependency
83
+ name: ace-runtime
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.1'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.1'
96
+ - !ruby/object:Gem::Dependency
97
+ name: ace-tmux
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.18'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.18'
82
110
  - !ruby/object:Gem::Dependency
83
111
  name: rake
84
112
  requirement: !ruby/object:Gem::Requirement