ace-git-worktree 0.21.8 → 0.22.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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/docs/usage.md +37 -3
  4. data/handbook/skills/as-git-worktree-cleanup/SKILL.md +20 -0
  5. data/handbook/workflow-instructions/git/worktree-cleanup.wf.md +63 -0
  6. data/handbook/workflow-instructions/git/worktree-create.wf.md +6 -0
  7. data/lib/ace/git/worktree/cli/commands/bootstrap.rb +43 -0
  8. data/lib/ace/git/worktree/cli/commands/cleanup.rb +45 -0
  9. data/lib/ace/git/worktree/cli/commands/config.rb +27 -4
  10. data/lib/ace/git/worktree/cli/commands/create.rb +1 -0
  11. data/lib/ace/git/worktree/cli.rb +6 -0
  12. data/lib/ace/git/worktree/commands/bootstrap_command.rb +159 -0
  13. data/lib/ace/git/worktree/commands/cleanup_command.rb +307 -0
  14. data/lib/ace/git/worktree/commands/config_command.rb +354 -181
  15. data/lib/ace/git/worktree/commands/create_command.rb +5 -0
  16. data/lib/ace/git/worktree/models/worktree_config.rb +33 -3
  17. data/lib/ace/git/worktree/molecules/bootstrap_executor.rb +99 -0
  18. data/lib/ace/git/worktree/molecules/cleanup_applier.rb +225 -0
  19. data/lib/ace/git/worktree/molecules/cleanup_pr_resolver.rb +213 -0
  20. data/lib/ace/git/worktree/molecules/cleanup_reporter.rb +396 -0
  21. data/lib/ace/git/worktree/molecules/config_loader.rb +1 -1
  22. data/lib/ace/git/worktree/molecules/task_committer.rb +55 -8
  23. data/lib/ace/git/worktree/molecules/toolchain_truster.rb +112 -0
  24. data/lib/ace/git/worktree/molecules/worktree_lister.rb +17 -1
  25. data/lib/ace/git/worktree/organisms/task_worktree_orchestrator.rb +159 -10
  26. data/lib/ace/git/worktree/organisms/worktree_manager.rb +4 -2
  27. data/lib/ace/git/worktree/version.rb +1 -1
  28. metadata +17 -6
@@ -28,7 +28,7 @@ module Ace
28
28
  @config = config || load_configuration
29
29
  @task_fetcher = Molecules::TaskFetcher.new
30
30
  @task_status_updater = Molecules::TaskStatusUpdater.new
31
- @task_committer = Molecules::TaskCommitter.new
31
+ @task_committer = Molecules::TaskCommitter.new(project_root: project_root)
32
32
  @task_pusher = Molecules::TaskPusher.new
33
33
  @worktree_creator = Molecules::WorktreeCreator.new
34
34
  @pr_creator = Molecules::PrCreator.new
@@ -80,6 +80,20 @@ module Ace
80
80
  ))
81
81
  end
82
82
 
83
+ # Snapshot pre-existing dirty state & resolve owned task paths
84
+ dirty_snapshot = snapshot_dirty_state
85
+ workflow_result[:preexisting_dirty] = dirty_snapshot
86
+ owned_paths = resolve_owned_task_paths(task_data)
87
+ workflow_result[:owned_task_paths] = owned_paths
88
+
89
+ # Check for pre-existing target conflicts on owned paths
90
+ all_dirty_files = dirty_snapshot[:staged] + dirty_snapshot[:unstaged] + dirty_snapshot[:untracked]
91
+ conflicting_paths = all_dirty_files & owned_paths
92
+ unless conflicting_paths.empty?
93
+ workflow_result[:recovery] = "Clean pre-existing edits on target task paths before creating worktree: #{conflicting_paths.join(', ')}"
94
+ return error_workflow_result("Pre-existing edits on target task paths: #{conflicting_paths.join(', ')}", workflow_result)
95
+ end
96
+
83
97
  # Step 3: Update task status if configured (and not overridden)
84
98
  should_update_status = options[:no_status_update] ? false : @config.auto_mark_in_progress?
85
99
  if should_update_status && task_data[:status] != "in-progress"
@@ -116,10 +130,21 @@ module Ace
116
130
  has_changes_to_commit = should_update_status || metadata_was_added
117
131
  if should_commit && has_changes_to_commit
118
132
  commit_message = options[:commit_message] || "in-progress"
119
- if commit_task_changes(task_data, commit_message)
120
- workflow_result[:steps_completed] << "task_committed"
133
+ commit_res = commit_task_changes(task_data, commit_message, owned_paths: owned_paths, explicit_message: options[:commit_message])
134
+ if commit_res[:success]
135
+ workflow_result[:bookkeeping_commit] = commit_res[:commit_sha]
136
+ workflow_result[:committed_paths] = commit_res[:committed_paths]
137
+
138
+ unowned_in_commit = commit_res[:committed_paths] - owned_paths
139
+ if unowned_in_commit.empty?
140
+ workflow_result[:path_set_verified] = true
141
+ workflow_result[:steps_completed] << "task_committed"
142
+ else
143
+ workflow_result[:path_set_verified] = false
144
+ workflow_result[:recovery] = "Bookkeeping commit #{commit_res[:commit_sha]} captured unowned paths: #{unowned_in_commit.join(', ')}"
145
+ return error_workflow_result("Bookkeeping commit captured unowned paths: #{unowned_in_commit.join(', ')}", workflow_result)
146
+ end
121
147
  else
122
- # Continue even if commit fails, but note it
123
148
  workflow_result[:warnings] << "Failed to commit task changes"
124
149
  end
125
150
  end
@@ -127,12 +152,18 @@ module Ace
127
152
  # Step 7: Push task changes if configured (so PR shows updates)
128
153
  should_push = options[:no_push] ? false : @config.auto_push_task?
129
154
  if should_push && should_commit && workflow_result[:steps_completed].include?("task_committed")
155
+ unless workflow_result[:path_set_verified]
156
+ workflow_result[:publication] = "not_attempted"
157
+ return error_workflow_result("Refusing to push unverified bookkeeping commit", workflow_result)
158
+ end
159
+
130
160
  push_remote = options[:push_remote] || @config.push_remote
131
161
  if push_task_changes(push_remote)
132
162
  workflow_result[:steps_completed] << "task_pushed"
133
163
  workflow_result[:pushed_to] = push_remote
164
+ workflow_result[:publication] = "pushed"
134
165
  else
135
- # Continue even if push fails, but note it
166
+ workflow_result[:publication] = "not_attempted"
136
167
  workflow_result[:warnings] << "Failed to push task changes"
137
168
  end
138
169
  end
@@ -247,6 +278,45 @@ module Ace
247
278
  end
248
279
  end
249
280
 
281
+ # Step 13: Execute toolchain trust and bootstrap preparation phases
282
+ require_relative "../molecules/toolchain_truster"
283
+ require_relative "../molecules/bootstrap_executor"
284
+
285
+ wt_path = worktree_result[:worktree_path]
286
+
287
+ if options[:no_mise_trust]
288
+ trust_res = {success: true, status: "not_applicable"}
289
+ else
290
+ policy = @config.mise_trust_auto? ? "required" : "advisory"
291
+ truster = Molecules::ToolchainTruster.new(project_root: wt_path, policy: policy)
292
+ trust_res = truster.verify_and_trust
293
+ end
294
+
295
+ executor = Molecules::BootstrapExecutor.new(
296
+ project_root: @project_root,
297
+ worktree_path: wt_path,
298
+ bootstrap_config: @config.bootstrap,
299
+ no_bootstrap: options[:no_bootstrap] || false
300
+ )
301
+ bootstrap_res = executor.run
302
+
303
+ workflow_result[:phases] = [trust_res, bootstrap_res]
304
+
305
+ req_failed = workflow_result[:phases].any? { |p| p[:status] == "required_failed" }
306
+ adv_failed = workflow_result[:phases].any? { |p| p[:status] == "advisory_failed" }
307
+
308
+ if req_failed
309
+ workflow_result[:readiness] = "not_ready"
310
+ workflow_result[:retry_command] = "ace-git-worktree bootstrap #{task_data[:id]}"
311
+ workflow_result[:success] = false
312
+ workflow_result[:error] = "Required preparation phase failed for #{wt_path}"
313
+ return workflow_result
314
+ elsif adv_failed
315
+ workflow_result[:readiness] = "ready_with_warning"
316
+ else
317
+ workflow_result[:readiness] = "ready"
318
+ end
319
+
250
320
  # Success!
251
321
  success_workflow_result("Task worktree created successfully", workflow_result)
252
322
  rescue => e
@@ -498,6 +568,14 @@ module Ace
498
568
  worktree_path: nil,
499
569
  branch: nil,
500
570
  directory_name: nil,
571
+ preexisting_dirty: {staged: [], unstaged: [], untracked: []},
572
+ owned_task_paths: [],
573
+ bookkeeping_commit: nil,
574
+ committed_paths: [],
575
+ index_preserved: true,
576
+ path_set_verified: false,
577
+ publication: "not_attempted",
578
+ recovery: nil,
501
579
  steps_completed: [],
502
580
  steps_planned: [],
503
581
  warnings: [],
@@ -506,6 +584,70 @@ module Ace
506
584
  }
507
585
  end
508
586
 
587
+ # Snapshot working tree and index dirty state
588
+ #
589
+ # @return [Hash] Hash with :staged, :unstaged, :untracked path arrays
590
+ def snapshot_dirty_state
591
+ staged = []
592
+ unstaged = []
593
+ untracked = []
594
+
595
+ result = Atoms::GitCommand.execute("status", "--porcelain=v1", timeout: 10)
596
+ if result[:success] && result[:output]
597
+ result[:output].each_line do |line|
598
+ line = line.chomp
599
+ next if line.empty?
600
+ x = line[0]
601
+ y = line[1]
602
+ file = line[3..-1].to_s.strip
603
+ file = file.split(" -> ").last if file.include?(" -> ")
604
+
605
+ if x == "?" && y == "?"
606
+ untracked << file
607
+ else
608
+ staged << file if x != " " && x != "?"
609
+ unstaged << file if y != " " && y != "?"
610
+ end
611
+ end
612
+ end
613
+
614
+ {staged: staged.uniq, unstaged: unstaged.uniq, untracked: untracked.uniq}
615
+ end
616
+
617
+ # Resolve relative task paths owned by ACE for this operation
618
+ #
619
+ # @param task_data [Hash] Task data hash
620
+ # @return [Array<String>] Array of relative file paths
621
+ def resolve_owned_task_paths(task_data)
622
+ paths = []
623
+ if task_data
624
+ raw_path = task_data[:path] || task_data["path"]
625
+ if raw_path
626
+ rel_path = relative_task_path(raw_path)
627
+ paths << rel_path if rel_path
628
+ end
629
+ end
630
+ paths.compact.uniq
631
+ end
632
+
633
+ # Make a task file path relative to project root
634
+ #
635
+ # @param path [String] Absolute or relative file path
636
+ # @return [String] Relative file path
637
+ def relative_task_path(path)
638
+ return nil unless path
639
+ require "pathname"
640
+ pn = Pathname.new(path)
641
+ root = Pathname.new(@project_root)
642
+ if pn.absolute?
643
+ pn.relative_path_from(root).to_s
644
+ else
645
+ path
646
+ end
647
+ rescue
648
+ path
649
+ end
650
+
509
651
  # Load configuration
510
652
  #
511
653
  # @return [WorktreeConfig] Loaded configuration
@@ -574,12 +716,19 @@ module Ace
574
716
  #
575
717
  # @param task_data [Hash] Task data hash from ace-task
576
718
  # @param status [String] Task status
577
- # @return [Boolean] true if successful
578
- def commit_task_changes(task_data, status)
579
- # Find the task file (this would need implementation)
580
- # For now, commit all changes
719
+ # @param owned_paths [Array<String>, nil] Target paths to commit
720
+ # @return [Hash] Result hash with :success, :commit_sha, :committed_paths
721
+ def commit_task_changes(task_data, status, owned_paths: nil, explicit_message: nil)
581
722
  task_id = extract_task_id(task_data)
582
- @task_committer.commit_all_changes(status, task_id)
723
+ paths = owned_paths || resolve_owned_task_paths(task_data)
724
+ return {success: false, commit_sha: nil, committed_paths: [], error: "No task paths"} if paths.empty?
725
+
726
+ message = explicit_message || begin
727
+ @config.format_commit_message(task_data)
728
+ rescue
729
+ "chore(task-#{task_id}): mark as #{status}"
730
+ end
731
+ @task_committer.commit_scoped(paths, message)
583
732
  end
584
733
 
585
734
  # Push task changes to remote
@@ -278,12 +278,14 @@ module Ace
278
278
  end
279
279
 
280
280
  # Apply filters
281
- if !options[:task_associated].nil? || !options[:usable].nil? || options[:search]
281
+ if !options[:task_associated].nil? || !options[:usable].nil? || options[:search] || options[:task_id] || options[:pr_number]
282
282
  worktrees = @worktree_lister.filter(
283
283
  worktrees,
284
284
  task_associated: options[:task_associated],
285
285
  usable: options[:usable],
286
- branch_pattern: options[:search]
286
+ branch_pattern: options[:search],
287
+ task_id: options[:task_id],
288
+ pr_number: options[:pr_number]
287
289
  )
288
290
  end
289
291
 
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Git
5
5
  module Worktree
6
- VERSION = "0.21.8"
6
+ VERSION = "0.22.0"
7
7
  end
8
8
  end
9
9
  end
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.21.8
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-08-12 00:00:00.000000000 Z
10
+ date: 2026-08-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -43,28 +43,28 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.17'
46
+ version: '0.18'
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.17'
53
+ version: '0.18'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: ace-git
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '0.22'
60
+ version: '0.23'
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.22'
67
+ version: '0.23'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: ace-task
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -148,9 +148,11 @@ files:
148
148
  - docs/usage.md
149
149
  - exe/ace-git-worktree
150
150
  - handbook/agents/worktree.ag.md
151
+ - handbook/skills/as-git-worktree-cleanup/SKILL.md
151
152
  - handbook/skills/as-git-worktree-create/SKILL.md
152
153
  - handbook/skills/as-git-worktree-manage/SKILL.md
153
154
  - handbook/skills/as-git-worktree/SKILL.md
155
+ - handbook/workflow-instructions/git/worktree-cleanup.wf.md
154
156
  - handbook/workflow-instructions/git/worktree-create.wf.md
155
157
  - handbook/workflow-instructions/git/worktree-manage.wf.md
156
158
  - handbook/workflow-instructions/git/worktree.wf.md
@@ -160,6 +162,8 @@ files:
160
162
  - lib/ace/git/worktree/atoms/slug_generator.rb
161
163
  - lib/ace/git/worktree/atoms/task_id_extractor.rb
162
164
  - lib/ace/git/worktree/cli.rb
165
+ - lib/ace/git/worktree/cli/commands/bootstrap.rb
166
+ - lib/ace/git/worktree/cli/commands/cleanup.rb
163
167
  - lib/ace/git/worktree/cli/commands/config.rb
164
168
  - lib/ace/git/worktree/cli/commands/create.rb
165
169
  - lib/ace/git/worktree/cli/commands/list.rb
@@ -167,6 +171,8 @@ files:
167
171
  - lib/ace/git/worktree/cli/commands/remove.rb
168
172
  - lib/ace/git/worktree/cli/commands/shared_helpers.rb
169
173
  - lib/ace/git/worktree/cli/commands/switch.rb
174
+ - lib/ace/git/worktree/commands/bootstrap_command.rb
175
+ - lib/ace/git/worktree/commands/cleanup_command.rb
170
176
  - lib/ace/git/worktree/commands/config_command.rb
171
177
  - lib/ace/git/worktree/commands/create_command.rb
172
178
  - lib/ace/git/worktree/commands/list_command.rb
@@ -177,6 +183,10 @@ files:
177
183
  - lib/ace/git/worktree/models/worktree_config.rb
178
184
  - lib/ace/git/worktree/models/worktree_info.rb
179
185
  - lib/ace/git/worktree/models/worktree_metadata.rb
186
+ - lib/ace/git/worktree/molecules/bootstrap_executor.rb
187
+ - lib/ace/git/worktree/molecules/cleanup_applier.rb
188
+ - lib/ace/git/worktree/molecules/cleanup_pr_resolver.rb
189
+ - lib/ace/git/worktree/molecules/cleanup_reporter.rb
180
190
  - lib/ace/git/worktree/molecules/config_loader.rb
181
191
  - lib/ace/git/worktree/molecules/current_task_linker.rb
182
192
  - lib/ace/git/worktree/molecules/hook_executor.rb
@@ -186,6 +196,7 @@ files:
186
196
  - lib/ace/git/worktree/molecules/task_fetcher.rb
187
197
  - lib/ace/git/worktree/molecules/task_pusher.rb
188
198
  - lib/ace/git/worktree/molecules/task_status_updater.rb
199
+ - lib/ace/git/worktree/molecules/toolchain_truster.rb
189
200
  - lib/ace/git/worktree/molecules/worktree_creator.rb
190
201
  - lib/ace/git/worktree/molecules/worktree_lister.rb
191
202
  - lib/ace/git/worktree/molecules/worktree_remover.rb