ace-git-worktree 0.21.6 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/docs/usage.md +37 -3
- data/handbook/skills/as-git-worktree-cleanup/SKILL.md +20 -0
- data/handbook/workflow-instructions/git/worktree-cleanup.wf.md +63 -0
- data/handbook/workflow-instructions/git/worktree-create.wf.md +6 -0
- data/lib/ace/git/worktree/cli/commands/bootstrap.rb +43 -0
- data/lib/ace/git/worktree/cli/commands/cleanup.rb +45 -0
- data/lib/ace/git/worktree/cli/commands/config.rb +27 -4
- data/lib/ace/git/worktree/cli/commands/create.rb +1 -0
- data/lib/ace/git/worktree/cli.rb +6 -0
- data/lib/ace/git/worktree/commands/bootstrap_command.rb +159 -0
- data/lib/ace/git/worktree/commands/cleanup_command.rb +307 -0
- data/lib/ace/git/worktree/commands/config_command.rb +354 -181
- data/lib/ace/git/worktree/commands/create_command.rb +5 -0
- data/lib/ace/git/worktree/models/worktree_config.rb +33 -3
- data/lib/ace/git/worktree/molecules/bootstrap_executor.rb +99 -0
- data/lib/ace/git/worktree/molecules/cleanup_applier.rb +225 -0
- data/lib/ace/git/worktree/molecules/cleanup_pr_resolver.rb +213 -0
- data/lib/ace/git/worktree/molecules/cleanup_reporter.rb +396 -0
- data/lib/ace/git/worktree/molecules/config_loader.rb +1 -1
- data/lib/ace/git/worktree/molecules/task_committer.rb +55 -8
- data/lib/ace/git/worktree/molecules/toolchain_truster.rb +112 -0
- data/lib/ace/git/worktree/molecules/worktree_lister.rb +17 -1
- data/lib/ace/git/worktree/organisms/task_worktree_orchestrator.rb +159 -10
- data/lib/ace/git/worktree/organisms/worktree_manager.rb +4 -2
- data/lib/ace/git/worktree/version.rb +1 -1
- metadata +23 -12
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
require "json"
|
|
2
|
+
require "yaml"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "../organisms/worktree_manager"
|
|
5
|
+
require_relative "../molecules/config_loader"
|
|
6
|
+
require_relative "../models/worktree_config"
|
|
2
7
|
|
|
3
8
|
module Ace
|
|
4
9
|
module Git
|
|
@@ -6,14 +11,8 @@ module Ace
|
|
|
6
11
|
module Commands
|
|
7
12
|
# Config command
|
|
8
13
|
#
|
|
9
|
-
# Displays and validates worktree configuration.
|
|
10
|
-
# Shows current settings,
|
|
11
|
-
#
|
|
12
|
-
# @example Show current configuration
|
|
13
|
-
# ConfigCommand.new.run(["--show"])
|
|
14
|
-
#
|
|
15
|
-
# @example Validate configuration
|
|
16
|
-
# ConfigCommand.new.run(["--validate"])
|
|
14
|
+
# Displays, initializes, updates, and validates worktree configuration.
|
|
15
|
+
# Shows current settings, provenance tracking, and validation results.
|
|
17
16
|
class ConfigCommand
|
|
18
17
|
# Initialize a new ConfigCommand
|
|
19
18
|
def initialize
|
|
@@ -28,31 +27,34 @@ module Ace
|
|
|
28
27
|
options = parse_arguments(args)
|
|
29
28
|
return show_help if options[:help]
|
|
30
29
|
|
|
30
|
+
if options[:init]
|
|
31
|
+
return init_project_config
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if options[:set_bootstrap]
|
|
35
|
+
return set_bootstrap_config(options)
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
# Default to showing configuration if no action specified
|
|
32
39
|
options[:show] = true unless options[:validate] || options[:show] || options[:files]
|
|
33
40
|
|
|
34
|
-
validate_options(options)
|
|
35
|
-
|
|
36
41
|
results = []
|
|
37
42
|
|
|
38
43
|
if options[:show]
|
|
39
|
-
results << show_configuration
|
|
44
|
+
results << show_configuration(json: options[:json])
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
if options[:validate]
|
|
43
|
-
results << validate_configuration
|
|
48
|
+
results << validate_configuration(bootstrap_only: options[:bootstrap], json: options[:json])
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
if options[:files]
|
|
47
52
|
results << show_configuration_files
|
|
48
53
|
end
|
|
49
54
|
|
|
50
|
-
# Return success if all operations succeeded
|
|
51
55
|
(results.all? { |result| result == 0 }) ? 0 : 1
|
|
52
56
|
rescue ArgumentError => e
|
|
53
57
|
puts "Error: #{e.message}"
|
|
54
|
-
puts
|
|
55
|
-
show_help
|
|
56
58
|
1
|
|
57
59
|
rescue => e
|
|
58
60
|
puts "Error: #{e.message}"
|
|
@@ -67,51 +69,31 @@ module Ace
|
|
|
67
69
|
ace-git-worktree config - Manage worktree configuration
|
|
68
70
|
|
|
69
71
|
USAGE:
|
|
70
|
-
ace-git-worktree config [OPTIONS]
|
|
72
|
+
ace-git-worktree config [SUBCOMMAND / OPTIONS]
|
|
71
73
|
|
|
72
74
|
ACTIONS:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
--
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
init Initialize minimal project worktree configuration
|
|
76
|
+
set-bootstrap Set bootstrap command policy
|
|
77
|
+
--show Show current configuration (default)
|
|
78
|
+
--validate Validate configuration
|
|
79
|
+
--files Show configuration file locations
|
|
80
|
+
|
|
81
|
+
SET BOOTSTRAP OPTIONS:
|
|
82
|
+
--command <cmd> Command to run
|
|
83
|
+
--working-dir <path> Working directory relative to project root
|
|
84
|
+
--timeout <sec> Timeout in seconds (max 300)
|
|
85
|
+
--required | --advisory Policy for execution failure
|
|
86
|
+
--env KEY=VALUE Environment variables
|
|
87
|
+
|
|
88
|
+
FORMAT OPTIONS:
|
|
89
|
+
--json Format output as JSON
|
|
90
|
+
--bootstrap Validate bootstrap section only
|
|
80
91
|
|
|
81
92
|
EXAMPLES:
|
|
82
|
-
|
|
83
|
-
ace-git-worktree config
|
|
84
|
-
ace-git-worktree config --
|
|
85
|
-
|
|
86
|
-
# Validate configuration
|
|
87
|
-
ace-git-worktree config --validate
|
|
88
|
-
|
|
89
|
-
# Show configuration file locations
|
|
90
|
-
ace-git-worktree config --files
|
|
91
|
-
|
|
92
|
-
# Show everything
|
|
93
|
-
ace-git-worktree config --show --validate --files
|
|
94
|
-
|
|
95
|
-
CONFIGURATION FILES:
|
|
96
|
-
.ace/git/worktree.yml Project-specific configuration
|
|
97
|
-
.ace-defaults/git/worktree.yml Example configuration template
|
|
98
|
-
~/.ace/git/worktree.yml User-specific configuration
|
|
99
|
-
|
|
100
|
-
CONFIGURATION OPTIONS:
|
|
101
|
-
git.worktree.root_path Worktree root directory
|
|
102
|
-
git.worktree.mise_trust_auto Automatic mise trust
|
|
103
|
-
git.worktree.task.* Task-related settings
|
|
104
|
-
git.worktree.cleanup.* Cleanup behavior settings
|
|
105
|
-
|
|
106
|
-
VALIDATION:
|
|
107
|
-
Checks for:
|
|
108
|
-
• Valid configuration structure
|
|
109
|
-
• Required configuration fields
|
|
110
|
-
• Accessible worktree root directory
|
|
111
|
-
• Valid template variables
|
|
112
|
-
• Consistent settings
|
|
113
|
-
|
|
114
|
-
For configuration examples, see .ace-defaults/git/worktree.yml
|
|
93
|
+
ace-git-worktree config init
|
|
94
|
+
ace-git-worktree config set-bootstrap --command "npm install" --timeout 120 --required
|
|
95
|
+
ace-git-worktree config show --json
|
|
96
|
+
ace-git-worktree config validate --bootstrap --json
|
|
115
97
|
HELP
|
|
116
98
|
0
|
|
117
99
|
end
|
|
@@ -127,6 +109,16 @@ module Ace
|
|
|
127
109
|
show: false,
|
|
128
110
|
validate: false,
|
|
129
111
|
files: false,
|
|
112
|
+
init: false,
|
|
113
|
+
set_bootstrap: false,
|
|
114
|
+
json: false,
|
|
115
|
+
bootstrap: false,
|
|
116
|
+
command: nil,
|
|
117
|
+
working_dir: nil,
|
|
118
|
+
timeout: nil,
|
|
119
|
+
required: false,
|
|
120
|
+
advisory: false,
|
|
121
|
+
env: [],
|
|
130
122
|
verbose: false,
|
|
131
123
|
help: false
|
|
132
124
|
}
|
|
@@ -136,12 +128,36 @@ module Ace
|
|
|
136
128
|
arg = args[i]
|
|
137
129
|
|
|
138
130
|
case arg
|
|
139
|
-
when "
|
|
131
|
+
when "init"
|
|
132
|
+
options[:init] = true
|
|
133
|
+
when "set-bootstrap"
|
|
134
|
+
options[:set_bootstrap] = true
|
|
135
|
+
when "show", "--show"
|
|
140
136
|
options[:show] = true
|
|
141
|
-
when "--validate"
|
|
137
|
+
when "validate", "--validate"
|
|
142
138
|
options[:validate] = true
|
|
143
|
-
when "--files"
|
|
139
|
+
when "files", "--files"
|
|
144
140
|
options[:files] = true
|
|
141
|
+
when "--json"
|
|
142
|
+
options[:json] = true
|
|
143
|
+
when "--bootstrap"
|
|
144
|
+
options[:bootstrap] = true
|
|
145
|
+
when "--command"
|
|
146
|
+
i += 1
|
|
147
|
+
options[:command] = args[i]
|
|
148
|
+
when "--working-dir"
|
|
149
|
+
i += 1
|
|
150
|
+
options[:working_dir] = args[i]
|
|
151
|
+
when "--timeout"
|
|
152
|
+
i += 1
|
|
153
|
+
options[:timeout] = args[i]
|
|
154
|
+
when "--required"
|
|
155
|
+
options[:required] = true
|
|
156
|
+
when "--advisory"
|
|
157
|
+
options[:advisory] = true
|
|
158
|
+
when "--env"
|
|
159
|
+
i += 1
|
|
160
|
+
options[:env] << args[i] if args[i]
|
|
145
161
|
when "--verbose", "-v"
|
|
146
162
|
options[:verbose] = true
|
|
147
163
|
when "--help", "-h"
|
|
@@ -149,15 +165,7 @@ module Ace
|
|
|
149
165
|
when /^--/
|
|
150
166
|
raise ArgumentError, "Unknown option: #{arg}"
|
|
151
167
|
else
|
|
152
|
-
|
|
153
|
-
case arg
|
|
154
|
-
when "show"
|
|
155
|
-
options[:show] = true
|
|
156
|
-
when "validate"
|
|
157
|
-
options[:validate] = true
|
|
158
|
-
else
|
|
159
|
-
raise ArgumentError, "Unexpected argument: #{arg}"
|
|
160
|
-
end
|
|
168
|
+
raise ArgumentError, "Unexpected argument: #{arg}"
|
|
161
169
|
end
|
|
162
170
|
|
|
163
171
|
i += 1
|
|
@@ -166,29 +174,150 @@ module Ace
|
|
|
166
174
|
options
|
|
167
175
|
end
|
|
168
176
|
|
|
169
|
-
#
|
|
177
|
+
# Initialize minimal project override configuration
|
|
178
|
+
#
|
|
179
|
+
# @return [Integer] Exit code
|
|
180
|
+
def init_project_config
|
|
181
|
+
project_root = Dir.pwd
|
|
182
|
+
ace_dir = File.join(project_root, ".ace", "git")
|
|
183
|
+
FileUtils.mkdir_p(ace_dir)
|
|
184
|
+
config_file = File.join(ace_dir, "worktree.yml")
|
|
185
|
+
|
|
186
|
+
if File.exist?(config_file)
|
|
187
|
+
begin
|
|
188
|
+
existing = YAML.safe_load_file(config_file, permitted_classes: [Date], aliases: true)
|
|
189
|
+
if existing && !existing.is_a?(Hash)
|
|
190
|
+
puts "Error: Incompatible existing project configuration at #{config_file}"
|
|
191
|
+
return 1
|
|
192
|
+
end
|
|
193
|
+
rescue => e
|
|
194
|
+
puts "Error: Incompatible existing project configuration at #{config_file}: #{e.message}"
|
|
195
|
+
return 1
|
|
196
|
+
end
|
|
197
|
+
puts "Project configuration already initialized at #{config_file}"
|
|
198
|
+
return 0
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
minimal = <<~YAML
|
|
202
|
+
# ACE Git Worktree Project Configuration
|
|
203
|
+
git:
|
|
204
|
+
worktree: {}
|
|
205
|
+
YAML
|
|
206
|
+
|
|
207
|
+
tmp_file = "#{config_file}.tmp.#{Process.pid}"
|
|
208
|
+
File.write(tmp_file, minimal)
|
|
209
|
+
File.rename(tmp_file, config_file)
|
|
210
|
+
puts "Initialized project worktree configuration at #{config_file}"
|
|
211
|
+
0
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Set bootstrap policy configuration
|
|
170
215
|
#
|
|
171
|
-
# @param options [Hash]
|
|
172
|
-
|
|
173
|
-
|
|
216
|
+
# @param options [Hash] Options with command, working_dir, timeout, required/advisory, env
|
|
217
|
+
# @return [Integer] Exit code
|
|
218
|
+
def set_bootstrap_config(options)
|
|
219
|
+
command = options[:command]
|
|
220
|
+
if command.nil? || command.strip.empty?
|
|
221
|
+
puts "Error: --command is required for set-bootstrap"
|
|
222
|
+
return 1
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
working_dir = options[:working_dir] || "."
|
|
226
|
+
if working_dir.start_with?("/") || working_dir.include?("..")
|
|
227
|
+
puts "Error: --working-dir must be a relative path within the project root"
|
|
228
|
+
return 1
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
timeout = (options[:timeout] || 60).to_i
|
|
232
|
+
if timeout <= 0 || timeout > 300
|
|
233
|
+
puts "Error: --timeout must be a positive integer <= 300"
|
|
234
|
+
return 1
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
if options[:required] && options[:advisory]
|
|
238
|
+
puts "Error: Cannot specify both --required and --advisory"
|
|
239
|
+
return 1
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
policy = options[:advisory] ? "advisory" : "required"
|
|
243
|
+
|
|
244
|
+
env_hash = {}
|
|
245
|
+
Array(options[:env]).each do |pair|
|
|
246
|
+
unless pair.include?("=")
|
|
247
|
+
puts "Error: Environment variables must be in KEY=VALUE format: #{pair}"
|
|
248
|
+
return 1
|
|
249
|
+
end
|
|
250
|
+
k, v = pair.split("=", 2)
|
|
251
|
+
env_hash[k.strip] = v.to_s
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
project_root = Dir.pwd
|
|
255
|
+
ace_dir = File.join(project_root, ".ace", "git")
|
|
256
|
+
FileUtils.mkdir_p(ace_dir)
|
|
257
|
+
config_file = File.join(ace_dir, "worktree.yml")
|
|
258
|
+
|
|
259
|
+
existing_yaml = {}
|
|
260
|
+
if File.exist?(config_file)
|
|
261
|
+
begin
|
|
262
|
+
loaded = YAML.safe_load_file(config_file, permitted_classes: [Date], aliases: true)
|
|
263
|
+
existing_yaml = loaded if loaded.is_a?(Hash)
|
|
264
|
+
rescue => e
|
|
265
|
+
puts "Error reading existing configuration: #{e.message}"
|
|
266
|
+
return 1
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
existing_yaml["git"] ||= {}
|
|
271
|
+
existing_yaml["git"]["worktree"] ||= {}
|
|
272
|
+
existing_yaml["git"]["worktree"]["bootstrap"] = {
|
|
273
|
+
"command" => command,
|
|
274
|
+
"working_dir" => working_dir,
|
|
275
|
+
"timeout" => timeout,
|
|
276
|
+
"policy" => policy,
|
|
277
|
+
"env" => env_hash
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
tmp_file = "#{config_file}.tmp.#{Process.pid}"
|
|
281
|
+
File.write(tmp_file, YAML.dump(existing_yaml))
|
|
282
|
+
File.rename(tmp_file, config_file)
|
|
283
|
+
puts "Updated bootstrap policy at #{config_file}"
|
|
284
|
+
0
|
|
174
285
|
end
|
|
175
286
|
|
|
176
|
-
# Show current configuration
|
|
287
|
+
# Show current configuration (text or JSON format)
|
|
177
288
|
#
|
|
289
|
+
# @param json [Boolean] Format as JSON
|
|
178
290
|
# @return [Integer] Exit code
|
|
179
|
-
def show_configuration
|
|
291
|
+
def show_configuration(json: false)
|
|
292
|
+
if json
|
|
293
|
+
json_data = build_show_json
|
|
294
|
+
puts JSON.pretty_generate(json_data)
|
|
295
|
+
return 0
|
|
296
|
+
end
|
|
297
|
+
|
|
180
298
|
puts "Current Worktree Configuration:"
|
|
181
299
|
puts "=" * 50
|
|
182
300
|
|
|
183
301
|
config = @manager.configuration
|
|
184
302
|
|
|
185
|
-
# Basic settings
|
|
186
303
|
puts "Root Path: #{config.root_path}"
|
|
187
304
|
puts "Absolute Root: #{config.absolute_root_path}"
|
|
188
305
|
puts "Mise Trust Auto: #{config.mise_trust_auto? ? "enabled" : "disabled"}"
|
|
189
306
|
puts
|
|
190
307
|
|
|
191
|
-
|
|
308
|
+
if config.bootstrap_configured?
|
|
309
|
+
bs = config.bootstrap
|
|
310
|
+
puts "Bootstrap Settings:"
|
|
311
|
+
puts " Command: #{bs["command"]}"
|
|
312
|
+
puts " Working Dir: #{bs["working_dir"] || "."}"
|
|
313
|
+
puts " Timeout: #{bs["timeout"]}s"
|
|
314
|
+
puts " Policy: #{bs["policy"] || "required"}"
|
|
315
|
+
puts
|
|
316
|
+
else
|
|
317
|
+
puts "Bootstrap Settings: not configured"
|
|
318
|
+
puts
|
|
319
|
+
end
|
|
320
|
+
|
|
192
321
|
puts "Task Settings:"
|
|
193
322
|
puts " Directory Format: #{config.directory_format}"
|
|
194
323
|
puts " Branch Format: #{config.branch_format}"
|
|
@@ -197,33 +326,6 @@ module Ace
|
|
|
197
326
|
puts " Add Worktree Metadata: #{config.add_worktree_metadata? ? "enabled" : "disabled"}"
|
|
198
327
|
puts
|
|
199
328
|
|
|
200
|
-
if config.auto_commit_task?
|
|
201
|
-
puts "Commit Message Format:"
|
|
202
|
-
puts " #{config.commit_message_format}"
|
|
203
|
-
puts
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Cleanup settings
|
|
207
|
-
puts "Cleanup Settings:"
|
|
208
|
-
puts " On Merge: #{config.cleanup_on_merge? ? "enabled" : "disabled"}"
|
|
209
|
-
puts " On Delete: #{config.cleanup_on_delete? ? "enabled" : "disabled"}"
|
|
210
|
-
puts
|
|
211
|
-
|
|
212
|
-
# Template variables
|
|
213
|
-
puts "Available Template Variables:"
|
|
214
|
-
puts " {id} - Task numeric ID (e.g., 081)"
|
|
215
|
-
puts " {task_id} - Full task ID (e.g., task.081)"
|
|
216
|
-
puts " {slug} - URL-safe slug from task title"
|
|
217
|
-
puts
|
|
218
|
-
|
|
219
|
-
# Example usage
|
|
220
|
-
puts "Example Usage:"
|
|
221
|
-
task_id = "081"
|
|
222
|
-
task_slug = "fix-authentication-bug"
|
|
223
|
-
puts " Directory: #{config.directory_format.gsub("{id}", task_id).gsub("{slug}", task_slug)}"
|
|
224
|
-
puts " Branch: #{config.branch_format.gsub("{id}", task_id).gsub("{slug}", task_slug)}"
|
|
225
|
-
puts
|
|
226
|
-
|
|
227
329
|
0
|
|
228
330
|
rescue => e
|
|
229
331
|
puts "Error showing configuration: #{e.message}"
|
|
@@ -232,47 +334,169 @@ module Ace
|
|
|
232
334
|
|
|
233
335
|
# Validate configuration
|
|
234
336
|
#
|
|
235
|
-
# @
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
337
|
+
# @param bootstrap_only [Boolean] Validate bootstrap section only
|
|
338
|
+
# @param json [Boolean] Output JSON format
|
|
339
|
+
def validate_configuration(bootstrap_only: false, json: false)
|
|
340
|
+
result = @manager.validate_configuration rescue nil
|
|
341
|
+
loader = Molecules::ConfigLoader.new(Dir.pwd)
|
|
342
|
+
config = loader.load_without_validation rescue nil
|
|
343
|
+
result ||= (config ? {success: config.validate.empty?, errors: config.validate} : {success: true, valid: true, errors: []})
|
|
344
|
+
errors = bootstrap_only ? [] : (result[:errors] || []).dup
|
|
345
|
+
|
|
346
|
+
if bootstrap_only || (config && config.bootstrap_configured?)
|
|
347
|
+
bs = config ? config.bootstrap : {}
|
|
348
|
+
if !config || !config.bootstrap_configured?
|
|
349
|
+
errors << "Bootstrap is not configured" if bootstrap_only
|
|
350
|
+
else
|
|
351
|
+
cmd = bs["command"]
|
|
352
|
+
errors << "Bootstrap command must be a non-empty string" if cmd.nil? || cmd.to_s.strip.empty?
|
|
239
353
|
|
|
240
|
-
|
|
354
|
+
wdir = bs["working_dir"] || "."
|
|
355
|
+
errors << "Bootstrap working_dir must be a relative path without leading slash or .." if wdir.start_with?("/") || wdir.include?("..")
|
|
241
356
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
puts
|
|
357
|
+
timeout = bs["timeout"].to_i
|
|
358
|
+
errors << "Bootstrap timeout must be > 0 and <= 300" if timeout <= 0 || timeout > 300
|
|
245
359
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
result[:errors].each { |error| puts " ⚠️ #{error}" }
|
|
360
|
+
policy = bs["policy"] || "required"
|
|
361
|
+
errors << "Bootstrap policy must be 'required' or 'advisory'" unless %w[required advisory].include?(policy)
|
|
249
362
|
end
|
|
250
|
-
|
|
251
|
-
puts "❌ Configuration validation failed"
|
|
252
|
-
puts
|
|
253
|
-
puts "Errors:"
|
|
254
|
-
result[:errors].each { |error| puts " ❌ #{error}" }
|
|
255
|
-
puts
|
|
363
|
+
end
|
|
256
364
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
365
|
+
valid = errors.empty? && (bootstrap_only || result[:success] != false)
|
|
366
|
+
|
|
367
|
+
if json
|
|
368
|
+
json_result = {
|
|
369
|
+
schema_version: "1.0",
|
|
370
|
+
valid: valid,
|
|
371
|
+
mode: bootstrap_only ? "bootstrap_only" : "full",
|
|
372
|
+
errors: errors
|
|
373
|
+
}
|
|
374
|
+
puts JSON.pretty_generate(json_result)
|
|
375
|
+
return valid ? 0 : 1
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
puts "Configuration Validation:"
|
|
379
|
+
puts "=" * 30
|
|
264
380
|
|
|
381
|
+
if valid
|
|
382
|
+
puts "✅ Configuration is valid"
|
|
383
|
+
else
|
|
384
|
+
puts "❌ Configuration validation failed"
|
|
385
|
+
errors.each { |err| puts " ❌ #{err}" }
|
|
265
386
|
return 1
|
|
266
387
|
end
|
|
267
388
|
|
|
268
|
-
puts
|
|
269
|
-
|
|
270
389
|
0
|
|
271
390
|
rescue => e
|
|
272
391
|
puts "Error validating configuration: #{e.message}"
|
|
273
392
|
1
|
|
274
393
|
end
|
|
275
394
|
|
|
395
|
+
# Safely load YAML file
|
|
396
|
+
def load_yaml_file(path)
|
|
397
|
+
return {} unless File.exist?(path)
|
|
398
|
+
YAML.safe_load_file(path, aliases: true)
|
|
399
|
+
rescue
|
|
400
|
+
begin
|
|
401
|
+
YAML.load_file(path) || {}
|
|
402
|
+
rescue
|
|
403
|
+
{}
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Build JSON representation of configuration and provenance
|
|
408
|
+
#
|
|
409
|
+
# @return [Hash] JSON structure
|
|
410
|
+
def build_show_json
|
|
411
|
+
loader = Molecules::ConfigLoader.new(Dir.pwd)
|
|
412
|
+
config = loader.load_without_validation
|
|
413
|
+
project_root = Dir.pwd
|
|
414
|
+
|
|
415
|
+
proj_file = File.join(project_root, ".ace", "git", "worktree.yml")
|
|
416
|
+
user_file = File.expand_path("~/.ace/git/worktree.yml")
|
|
417
|
+
pkg_file = File.join(project_root, ".ace-defaults", "git", "worktree.yml")
|
|
418
|
+
|
|
419
|
+
proj_data = load_yaml_file(proj_file)
|
|
420
|
+
user_data = load_yaml_file(user_file)
|
|
421
|
+
|
|
422
|
+
provenance_for = lambda do |*keys|
|
|
423
|
+
has_proj = proj_data.is_a?(Hash) && !proj_data.dig("git", "worktree", *keys).nil?
|
|
424
|
+
has_user = user_data.is_a?(Hash) && !user_data.dig("git", "worktree", *keys).nil?
|
|
425
|
+
|
|
426
|
+
if has_proj
|
|
427
|
+
"project"
|
|
428
|
+
elsif has_user
|
|
429
|
+
"user"
|
|
430
|
+
else
|
|
431
|
+
"package_default"
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
redact_env = lambda do |env_h|
|
|
436
|
+
return {} unless env_h.is_a?(Hash)
|
|
437
|
+
env_h.each_with_object({}) do |(k, v), acc|
|
|
438
|
+
if k.to_s.match?(/(secret|token|key|pass|auth|credential)/i)
|
|
439
|
+
acc[k.to_s] = "[REDACTED]"
|
|
440
|
+
else
|
|
441
|
+
acc[k.to_s] = v.to_s
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
bootstrap_h = config.bootstrap
|
|
447
|
+
if config.bootstrap_configured?
|
|
448
|
+
effective_bootstrap = {
|
|
449
|
+
"command" => bootstrap_h["command"],
|
|
450
|
+
"working_dir" => bootstrap_h["working_dir"] || ".",
|
|
451
|
+
"timeout" => (bootstrap_h["timeout"] || 60).to_i,
|
|
452
|
+
"policy" => bootstrap_h["policy"] || "required",
|
|
453
|
+
"env" => redact_env.call(bootstrap_h["env"])
|
|
454
|
+
}
|
|
455
|
+
else
|
|
456
|
+
effective_bootstrap = "not_configured"
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
provenance = {
|
|
460
|
+
"root_path" => provenance_for.call("root_path"),
|
|
461
|
+
"auto_navigate" => provenance_for.call("auto_navigate"),
|
|
462
|
+
"tmux" => provenance_for.call("tmux"),
|
|
463
|
+
"mise_trust_auto" => provenance_for.call("mise_trust_auto")
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if config.bootstrap_configured?
|
|
467
|
+
provenance["bootstrap.command"] = provenance_for.call("bootstrap", "command")
|
|
468
|
+
provenance["bootstrap.working_dir"] = provenance_for.call("bootstrap", "working_dir")
|
|
469
|
+
provenance["bootstrap.timeout"] = provenance_for.call("bootstrap", "timeout")
|
|
470
|
+
provenance["bootstrap.policy"] = provenance_for.call("bootstrap", "policy")
|
|
471
|
+
if bootstrap_h["env"].is_a?(Hash)
|
|
472
|
+
bootstrap_h["env"].each_key do |ek|
|
|
473
|
+
provenance["bootstrap.env.#{ek}"] = provenance_for.call("bootstrap", "env", ek)
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
validation_errors = config.validate
|
|
479
|
+
{
|
|
480
|
+
schema_version: "1.0",
|
|
481
|
+
config_files: [
|
|
482
|
+
{path: proj_file, exists: File.exist?(proj_file), type: "project"},
|
|
483
|
+
{path: user_file, exists: File.exist?(user_file), type: "user"},
|
|
484
|
+
{path: pkg_file, exists: File.exist?(pkg_file), type: "package"}
|
|
485
|
+
],
|
|
486
|
+
effective: {
|
|
487
|
+
root_path: config.root_path,
|
|
488
|
+
auto_navigate: config.auto_navigate?,
|
|
489
|
+
mise_trust_auto: config.mise_trust_auto?,
|
|
490
|
+
bootstrap: effective_bootstrap
|
|
491
|
+
},
|
|
492
|
+
provenance: provenance,
|
|
493
|
+
validation: {
|
|
494
|
+
valid: validation_errors.empty?,
|
|
495
|
+
errors: validation_errors
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
end
|
|
499
|
+
|
|
276
500
|
# Show configuration file locations
|
|
277
501
|
#
|
|
278
502
|
# @return [Integer] Exit code
|
|
@@ -280,69 +504,18 @@ module Ace
|
|
|
280
504
|
puts "Configuration Files:"
|
|
281
505
|
puts "=" * 20
|
|
282
506
|
|
|
283
|
-
# Get configuration files from config loader
|
|
284
507
|
config_loader = @manager.instance_variable_get(:@config_loader)
|
|
285
508
|
config_files = config_loader.config_files
|
|
286
509
|
|
|
287
510
|
config_files.each do |file|
|
|
288
511
|
if File.exist?(file)
|
|
289
512
|
puts "✅ #{file} (exists)"
|
|
290
|
-
|
|
291
|
-
# Show some info about the file
|
|
292
|
-
begin
|
|
293
|
-
stat = File.stat(file)
|
|
294
|
-
size = stat.size
|
|
295
|
-
mtime = stat.mtime.strftime("%Y-%m-%d %H:%M:%S")
|
|
296
|
-
puts " Size: #{format_bytes(size)}"
|
|
297
|
-
puts " Modified: #{mtime}"
|
|
298
|
-
|
|
299
|
-
# Check if it's the active config
|
|
300
|
-
if file.include?(".ace/git/worktree.yml")
|
|
301
|
-
puts " 📍 Active project configuration"
|
|
302
|
-
elsif file.include?(".ace-defaults/")
|
|
303
|
-
puts " 📋 Example template"
|
|
304
|
-
elsif file.include?("~/.ace/")
|
|
305
|
-
puts " 👤 User configuration"
|
|
306
|
-
end
|
|
307
|
-
rescue => e
|
|
308
|
-
puts " ⚠️ Error reading file info: #{e.message}"
|
|
309
|
-
end
|
|
310
513
|
else
|
|
311
514
|
puts "❌ #{file} (not found)"
|
|
312
515
|
end
|
|
313
|
-
puts
|
|
314
516
|
end
|
|
315
517
|
|
|
316
|
-
# Show configuration cascade order
|
|
317
|
-
puts "Configuration Priority (highest to lowest):"
|
|
318
|
-
puts " 1. .ace/git/worktree.yml (project-specific)"
|
|
319
|
-
puts " 2. ~/.ace/git/worktree.yml (user-specific)"
|
|
320
|
-
puts " 3. .ace-defaults/git/worktree.yml (defaults)"
|
|
321
|
-
puts
|
|
322
|
-
puts "Note: Later configurations override earlier ones."
|
|
323
|
-
puts
|
|
324
|
-
|
|
325
518
|
0
|
|
326
|
-
rescue => e
|
|
327
|
-
puts "Error showing configuration files: #{e.message}"
|
|
328
|
-
1
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
# Format bytes in human readable format
|
|
332
|
-
#
|
|
333
|
-
# @param bytes [Integer] Number of bytes
|
|
334
|
-
# @return [String] Formatted string
|
|
335
|
-
def format_bytes(bytes)
|
|
336
|
-
units = %w[B KB MB GB]
|
|
337
|
-
size = bytes.to_f
|
|
338
|
-
unit_index = 0
|
|
339
|
-
|
|
340
|
-
while size >= 1024 && unit_index < units.length - 1
|
|
341
|
-
size /= 1024
|
|
342
|
-
unit_index += 1
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
"#{size.round(1)} #{units[unit_index]}"
|
|
346
519
|
end
|
|
347
520
|
end
|
|
348
521
|
end
|