ukiryu 0.1.1 → 0.1.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +58 -14
  3. data/.gitignore +3 -0
  4. data/.rubocop_todo.yml +170 -79
  5. data/Gemfile +1 -1
  6. data/README.adoc +1603 -576
  7. data/docs/.gitignore +1 -0
  8. data/docs/Gemfile +10 -0
  9. data/docs/INDEX.adoc +261 -0
  10. data/docs/_config.yml +180 -0
  11. data/docs/advanced/custom-tool-classes.adoc +581 -0
  12. data/docs/advanced/index.adoc +20 -0
  13. data/docs/features/configuration.adoc +657 -0
  14. data/docs/features/index.adoc +31 -0
  15. data/docs/features/platform-support.adoc +488 -0
  16. data/docs/getting-started/core-concepts.adoc +666 -0
  17. data/docs/getting-started/index.adoc +36 -0
  18. data/docs/getting-started/installation.adoc +216 -0
  19. data/docs/getting-started/quick-start.adoc +258 -0
  20. data/docs/guides/env-var-sets.adoc +388 -0
  21. data/docs/guides/index.adoc +20 -0
  22. data/docs/interfaces/cli.adoc +609 -0
  23. data/docs/interfaces/index.adoc +153 -0
  24. data/docs/interfaces/ruby-api.adoc +538 -0
  25. data/docs/lychee.toml +49 -0
  26. data/docs/reference/configuration-options.adoc +720 -0
  27. data/docs/reference/error-codes.adoc +634 -0
  28. data/docs/reference/index.adoc +20 -0
  29. data/docs/reference/ruby-api.adoc +1217 -0
  30. data/docs/understanding/index.adoc +20 -0
  31. data/lib/ukiryu/cli.rb +43 -58
  32. data/lib/ukiryu/cli_commands/base_command.rb +16 -27
  33. data/lib/ukiryu/cli_commands/cache_command.rb +100 -0
  34. data/lib/ukiryu/cli_commands/commands_command.rb +8 -8
  35. data/lib/ukiryu/cli_commands/commands_command.rb.fixed +1 -1
  36. data/lib/ukiryu/cli_commands/config_command.rb +49 -7
  37. data/lib/ukiryu/cli_commands/definitions_command.rb +254 -0
  38. data/lib/ukiryu/cli_commands/describe_command.rb +13 -7
  39. data/lib/ukiryu/cli_commands/describe_command.rb.fixed +1 -1
  40. data/lib/ukiryu/cli_commands/docs_command.rb +148 -0
  41. data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +1 -1
  42. data/lib/ukiryu/cli_commands/extract_command.rb +2 -2
  43. data/lib/ukiryu/cli_commands/info_command.rb +7 -7
  44. data/lib/ukiryu/cli_commands/lint_command.rb +167 -0
  45. data/lib/ukiryu/cli_commands/list_command.rb +6 -6
  46. data/lib/ukiryu/cli_commands/opts_command.rb +2 -2
  47. data/lib/ukiryu/cli_commands/opts_command.rb.fixed +1 -1
  48. data/lib/ukiryu/cli_commands/register_command.rb +144 -0
  49. data/lib/ukiryu/cli_commands/resolve_command.rb +124 -0
  50. data/lib/ukiryu/cli_commands/run_command.rb +38 -14
  51. data/lib/ukiryu/cli_commands/run_file_command.rb +2 -2
  52. data/lib/ukiryu/cli_commands/system_command.rb +50 -32
  53. data/lib/ukiryu/cli_commands/validate_command.rb +452 -51
  54. data/lib/ukiryu/cli_commands/which_command.rb +5 -5
  55. data/lib/ukiryu/command_builder.rb +81 -23
  56. data/lib/ukiryu/config/env_provider.rb +3 -3
  57. data/lib/ukiryu/config/env_schema.rb +6 -6
  58. data/lib/ukiryu/config.rb +11 -11
  59. data/lib/ukiryu/definition/definition_cache.rb +238 -0
  60. data/lib/ukiryu/definition/definition_composer.rb +257 -0
  61. data/lib/ukiryu/definition/definition_linter.rb +460 -0
  62. data/lib/ukiryu/definition/definition_validator.rb +320 -0
  63. data/lib/ukiryu/definition/discovery.rb +239 -0
  64. data/lib/ukiryu/definition/documentation_generator.rb +429 -0
  65. data/lib/ukiryu/definition/lint_issue.rb +168 -0
  66. data/lib/ukiryu/definition/loader.rb +139 -0
  67. data/lib/ukiryu/definition/metadata.rb +159 -0
  68. data/lib/ukiryu/definition/source.rb +87 -0
  69. data/lib/ukiryu/definition/sources/file.rb +138 -0
  70. data/lib/ukiryu/definition/sources/string.rb +88 -0
  71. data/lib/ukiryu/definition/validation_result.rb +158 -0
  72. data/lib/ukiryu/definition/version_resolver.rb +194 -0
  73. data/lib/ukiryu/definition.rb +40 -0
  74. data/lib/ukiryu/errors.rb +6 -0
  75. data/lib/ukiryu/execution_context.rb +11 -11
  76. data/lib/ukiryu/executor.rb +6 -0
  77. data/lib/ukiryu/extractors/extractor.rb +6 -5
  78. data/lib/ukiryu/extractors/help_parser.rb +13 -19
  79. data/lib/ukiryu/logger.rb +3 -1
  80. data/lib/ukiryu/models/command_definition.rb +3 -3
  81. data/lib/ukiryu/models/command_info.rb +1 -1
  82. data/lib/ukiryu/models/components.rb +1 -3
  83. data/lib/ukiryu/models/env_var_definition.rb +11 -3
  84. data/lib/ukiryu/models/flag_definition.rb +15 -0
  85. data/lib/ukiryu/models/option_definition.rb +7 -7
  86. data/lib/ukiryu/models/platform_profile.rb +6 -3
  87. data/lib/ukiryu/models/routing.rb +1 -1
  88. data/lib/ukiryu/models/tool_definition.rb +2 -4
  89. data/lib/ukiryu/models/tool_metadata.rb +6 -6
  90. data/lib/ukiryu/models/validation_result.rb +1 -1
  91. data/lib/ukiryu/models/version_compatibility.rb +6 -3
  92. data/lib/ukiryu/models/version_detection.rb +10 -1
  93. data/lib/ukiryu/{registry.rb → register.rb} +54 -38
  94. data/lib/ukiryu/register_auto_manager.rb +268 -0
  95. data/lib/ukiryu/schema_validator.rb +31 -10
  96. data/lib/ukiryu/shell/base.rb +18 -0
  97. data/lib/ukiryu/shell/bash.rb +19 -1
  98. data/lib/ukiryu/shell/cmd.rb +11 -1
  99. data/lib/ukiryu/shell/powershell.rb +11 -1
  100. data/lib/ukiryu/shell.rb +1 -1
  101. data/lib/ukiryu/tool.rb +107 -95
  102. data/lib/ukiryu/tool_index.rb +22 -22
  103. data/lib/ukiryu/tools/base.rb +12 -25
  104. data/lib/ukiryu/tools/generator.rb +7 -7
  105. data/lib/ukiryu/tools.rb +3 -3
  106. data/lib/ukiryu/type.rb +20 -5
  107. data/lib/ukiryu/version.rb +1 -1
  108. data/lib/ukiryu/version_detector.rb +21 -2
  109. data/lib/ukiryu.rb +6 -3
  110. data/ukiryu-proposal.md +41 -41
  111. data/ukiryu.gemspec +1 -0
  112. metadata +64 -8
  113. data/.gitmodules +0 -3
@@ -28,51 +28,46 @@ module Ukiryu
28
28
  require_relative '../shell'
29
29
 
30
30
  all_shells = Shell.all_valid
31
- available_shells = Shell.available_shells
32
31
  platform_shells = Shell.valid_for_platform
32
+ available_shells = platform_shells.select { |shell| Shell.available?(shell) }
33
+ not_installed_shells = platform_shells.reject { |shell| Shell.available?(shell) }
34
+ not_supported_shells = all_shells - platform_shells
33
35
 
34
- say 'Available Shells on This System:', :cyan
36
+ say 'Available shells', :cyan
37
+ say ''
38
+ say ' The following shells are installed and supported on this platform:'
35
39
  say ''
36
40
 
37
41
  if available_shells.empty?
38
- say ' No shells detected', :dim
42
+ say ' No supported shells detected', :dim
39
43
  else
40
44
  available_shells.each do |shell|
41
- status = '✓'
42
- say " #{status} #{shell}", :green
45
+ say " • #{shell_name_with_description(shell)}", :green
43
46
  end
44
47
  end
45
48
 
46
- say ''
47
- say 'All Supported Shells:', :cyan
48
- say ''
49
-
50
- all_shells.each do |shell|
51
- is_available = available_shells.include?(shell)
52
- is_platform = platform_shells.include?(shell)
49
+ if not_installed_shells.any?
50
+ say ''
51
+ say 'Additional supported shells', :cyan
52
+ say ''
53
+ say ' These shells are supported but not currently installed:'
54
+ say ''
53
55
 
54
- status = if is_available
55
- '✓'
56
- elsif is_platform
57
- '✗'
58
- else
59
- '-'
60
- end
56
+ not_installed_shells.each do |shell|
57
+ say " • #{shell_name_with_description(shell)}", :dim
58
+ end
59
+ end
61
60
 
62
- color = if is_available
63
- :green
64
- else
65
- (is_platform ? :red : :dim)
66
- end
67
- note = if !is_platform
68
- ' (not supported on this platform)'
69
- elsif !is_available
70
- ' (supported but not found)'
71
- else
72
- ''
73
- end
61
+ if not_supported_shells.any?
62
+ say ''
63
+ say 'Not available on this platform', :cyan
64
+ say ''
65
+ say ' These shells are not supported on this platform:'
66
+ say ''
74
67
 
75
- say " #{status} #{shell}#{note}", color
68
+ not_supported_shells.each do |shell|
69
+ say " • #{shell_name_with_description(shell)}", :dim
70
+ end
76
71
  end
77
72
 
78
73
  say ''
@@ -85,6 +80,29 @@ module Ukiryu
85
80
 
86
81
  say "Shell override: #{config_shell} (set via --shell, UKIRYU_SHELL, or config)", :yellow
87
82
  end
83
+
84
+ # Get shell name with brief description
85
+ #
86
+ # @param shell_sym [Symbol] the shell symbol
87
+ # @return [String] formatted name with description
88
+ def shell_name_with_description(shell_sym)
89
+ case shell_sym
90
+ when :bash
91
+ 'bash - GNU Bourne Again SHell'
92
+ when :zsh
93
+ 'zsh - Z shell'
94
+ when :fish
95
+ 'fish - Friendly interactive shell'
96
+ when :sh
97
+ 'sh - POSIX shell'
98
+ when :powershell
99
+ 'powershell - PowerShell command-line shell'
100
+ when :cmd
101
+ 'cmd - Windows Command Prompt'
102
+ else
103
+ shell_sym.to_s
104
+ end
105
+ end
88
106
  end
89
107
  end
90
108
  end
@@ -1,86 +1,487 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_command'
4
- require_relative '../registry'
5
- require_relative '../models/validation_result'
3
+ require 'thor'
4
+ require_relative '../definition/definition_validator'
5
+ require_relative '../tool'
6
6
 
7
7
  module Ukiryu
8
8
  module CliCommands
9
- # Schema validation command for tool profiles
10
- class ValidateCommand < BaseCommand
11
- # Execute the validate command
12
- #
13
- # @param tool_name [String, nil] the tool name (nil to validate all)
14
- # @param options [Hash] command options
15
- def run(tool_name = nil)
16
- setup_registry
9
+ # Validate tool definitions
10
+ #
11
+ # The validate command checks tool definitions against JSON Schema
12
+ # and structural validation rules.
13
+ class ValidateCommand < Thor
14
+ class_option :verbose, type: :boolean, default: false
15
+ class_option :format, type: :string, default: 'text', enum: %w[text json]
16
+ class_option :schema, type: :string, desc: 'Path to JSON Schema file'
17
+ class_option :strict, type: :boolean, default: false, desc: 'Treat warnings as errors'
18
+ class_option :executable, type: :boolean, default: false, desc: 'Test executable against actual tool'
19
+ class_option :all, type: :boolean, default: false, desc: 'Enable all validations (schema + executable + smoke tests)'
20
+ class_option :register, type: :string, desc: 'Path to tool register'
17
21
 
18
- if tool_name
19
- validate_single_tool(tool_name)
20
- else
21
- validate_all_tools
22
- end
22
+ desc 'file PATH', 'Validate a definition file'
23
+ def file(path)
24
+ validate_file(path)
25
+ end
26
+
27
+ desc 'all', 'Validate all definitions in register'
28
+ option :register, type: :string, desc: 'Register path'
29
+ def all
30
+ validate_all
31
+ end
32
+
33
+ desc 'string YAML', 'Validate a YAML string'
34
+ def string(yaml)
35
+ validate_string(yaml)
23
36
  end
24
37
 
25
38
  private
26
39
 
27
- # Validate a single tool
40
+ # Validate a single file
28
41
  #
29
- # @param tool_name [String] the tool name
30
- def validate_single_tool(tool_name)
31
- result = Registry.validate_tool(tool_name, registry_path: config.registry)
42
+ # @param path [String] file path
43
+ def validate_file(path)
44
+ validate_file_impl(path)
45
+ end
32
46
 
33
- say "Validating tool: #{tool_name}", :cyan
47
+ # Validate a single file (implementation)
48
+ #
49
+ # @param path [String] file path
50
+ def validate_file_impl(path)
51
+ # First, validate the definition structure
52
+ result = Ukiryu::Definition::DefinitionValidator.validate_file(
53
+ path,
54
+ schema_path: options[:schema]
55
+ )
56
+
57
+ output_result(result, path)
58
+
59
+ # If structural validation passed and --executable or --all flag is set, test the executable
60
+ test_executable(path) if (options[:executable] || options[:all]) && result.valid?
61
+
62
+ exit 1 if result.invalid? || (result.has_warnings? && options[:strict])
63
+ end
64
+
65
+ # Test the executable against the actual tool
66
+ #
67
+ # @param path [String] path to definition file
68
+ def test_executable(path)
34
69
  say '', :clear
70
+ say 'Testing executable...', :cyan
71
+
72
+ begin
73
+ # Load the YAML profile to get smoke_tests
74
+ profile_data = YAML.safe_load(File.read(path), permitted_classes: [Symbol, Date, Time])
75
+
76
+ # Extract tool name from file path
77
+ file_name = File.basename(path, '.yaml')
78
+ tool_dir = File.basename(File.dirname(path))
79
+ tools_dir = File.basename(File.dirname(File.dirname(path)))
80
+
81
+ # The tool name is the directory name under tools/
82
+ tool_name = if tools_dir == 'tools'
83
+ tool_dir
84
+ else
85
+ file_name
86
+ end
87
+
88
+ # Don't hardcode register path - use options or let Tool.find handle it
89
+ tool_options = {}
90
+ tool_options[:register_path] = options[:register] if options[:register]
91
+
92
+ # Load the tool (Tool.get will find the right register path)
93
+ tool = Ukiryu::Tool.get(tool_name, **tool_options)
94
+
95
+ # Check if the tool is available
96
+ if tool.available?
97
+ say "✓ Tool found at: #{tool.executable}", :green
98
+ else
99
+ say '✗ Tool not found', :red
100
+ say " Searched in: #{tool.search_paths.join(', ')}", :dim
101
+ exit 1
102
+ end
103
+
104
+ # Test version detection if defined
105
+ if tool.profile.version_detection
106
+ say '', :clear
107
+ say 'Testing version detection...', :cyan
108
+
109
+ begin
110
+ detected_version = tool.detect_version
111
+
112
+ if detected_version
113
+ say "✓ Version detected: #{detected_version}", :green
114
+ else
115
+ say '⚠ Version detection failed - could not extract version', :yellow
116
+ end
117
+ rescue StandardError => e
118
+ say "⚠ Version check failed: #{e.message}", :yellow
119
+ end
120
+ end
121
+
122
+ # Run smoke tests from profile if defined
123
+ smoke_tests = profile_data[:smoke_tests] || profile_data['smoke_tests']
124
+ if smoke_tests && !smoke_tests.empty?
125
+ say '', :clear
126
+ say "Running #{smoke_tests.length} smoke test(s)...", :cyan
127
+ run_smoke_tests(tool, smoke_tests, profile_data)
128
+ elsif tool.profile.profiles&.any?
129
+ # Fallback: test basic command execution if commands are defined
130
+ profile = tool.profile.profiles.first
131
+ if profile.commands && !profile.commands.empty?
132
+ say '', :clear
133
+ say 'Testing command execution (smoke test)...', :cyan
134
+
135
+ profile.commands.each do |cmd_def|
136
+ cmd_name = cmd_def.name
137
+ begin
138
+ test_result = execute_smoke_test(tool, cmd_name)
139
+
140
+ if test_result[:success]
141
+ say "✓ Command '#{cmd_name}' is available", :green
142
+ else
143
+ say "⚠ Command '#{cmd_name}' test failed: #{test_result[:message]}", :yellow
144
+ end
145
+ rescue StandardError => e
146
+ say "⚠ Command '#{cmd_name}' test error: #{e.message}", :yellow
147
+ end
148
+
149
+ break
150
+ end
151
+ end
152
+ end
153
+ rescue Ukiryu::ToolNotFoundError => e
154
+ say "✗ Tool not found: #{e.message}", :red
155
+ exit 1
156
+ rescue StandardError => e
157
+ say "✗ Executable test failed: #{e.message}", :red
158
+ exit 1 if options[:strict]
159
+ end
160
+ end
161
+
162
+ # Run smoke tests from profile
163
+ #
164
+ # @param tool [Ukiryu::Tool] the tool instance
165
+ # @param smoke_tests [Array<Hash>] smoke test definitions
166
+ # @param profile_data [Hash] the loaded profile data
167
+ def run_smoke_tests(tool, smoke_tests, profile_data)
168
+ smoke_tests.each_with_index do |test, index|
169
+ test_name = test[:name] || test['name']
170
+ test_description = test[:description] || test['description'] || test_name
35
171
 
36
- if result.valid?
37
- say result.status_message, :green
38
- else
39
- say result.status_message, :red
40
172
  say '', :clear
41
- result.errors.each do |error|
42
- say " - #{error}", :white
173
+ say "[#{index + 1}/#{smoke_tests.length}] #{test_name}: #{test_description}", :cyan
174
+
175
+ # Check platform filter
176
+ platforms = test[:platforms] || test['platforms']
177
+ current_platform = current_platform_symbol
178
+ if platforms && !platforms.include?(current_platform.to_s)
179
+ say " ⊘ Skipped (not for this platform: #{current_platform})", :dim
180
+ next
181
+ end
182
+
183
+ # Check skip_if condition
184
+ skip_if = test[:skip_if] || test['skip_if']
185
+ if skip_if && evaluate_condition(skip_if, tool, profile_data)
186
+ say " ⊘ Skipped (condition: #{skip_if})", :dim
187
+ next
188
+ end
189
+
190
+ # Get the command to run
191
+ test_command = test[:command] || test['command']
192
+ test_timeout = test[:timeout] || test['timeout'] || tool.profile.timeout || 30
193
+
194
+ begin
195
+ # Execute the test command
196
+ result = execute_test_command(tool, test_command, test_timeout)
197
+
198
+ # Validate the result
199
+ validation_result = validate_test_result(result, test)
200
+
201
+ if validation_result[:passed]
202
+ say " ✓ PASSED", :green
203
+ if options[:verbose] && validation_result[:details]
204
+ validation_result[:details].each do |detail|
205
+ say " #{detail}", :dim
206
+ end
207
+ end
208
+ else
209
+ say " ✗ FAILED", :red
210
+ validation_result[:errors].each do |error|
211
+ say " ✗ #{error}", :red
212
+ end
213
+ exit 1 if options[:strict]
214
+ end
215
+ rescue StandardError => e
216
+ say " ✗ ERROR: #{e.message}", :red
217
+ exit 1 if options[:strict]
43
218
  end
44
219
  end
220
+ end
221
+
222
+ # Execute a test command
223
+ #
224
+ # @param tool [Ukiryu::Tool] the tool instance
225
+ # @param test_command [String, Array] command to run
226
+ # @param timeout [Integer] timeout in seconds
227
+ # @return [Hash] execution result
228
+ def execute_test_command(tool, test_command, _timeout)
229
+ cmd_array = if test_command.is_a?(Array)
230
+ test_command
231
+ else
232
+ # Parse command string into array (basic parsing)
233
+ test_command.shellsplit
234
+ end
235
+
236
+ # Build full command with tool executable
237
+ full_command = [tool.executable] + cmd_array
45
238
 
46
- # Exit with error code if validation failed
47
- exit(result.invalid? ? 1 : 0)
239
+ # Execute using Open3
240
+ require 'open3'
241
+ stdout_str, stderr_str, status = Open3.capture3(*full_command)
242
+
243
+ {
244
+ stdout: stdout_str,
245
+ stderr: stderr_str,
246
+ exit_code: status.exitstatus,
247
+ success: status.success?,
248
+ runtime: nil # TODO: add runtime measurement
249
+ }
48
250
  end
49
251
 
50
- # Validate all tools
51
- def validate_all_tools
52
- results = Registry.validate_all_tools(registry_path: config.registry)
252
+ # Validate test result against expectations
253
+ #
254
+ # @param result [Hash] execution result
255
+ # @param test [Hash] test definition with expect section
256
+ # @return [Hash] validation result with :passed, :errors, :details
257
+ def validate_test_result(result, test)
258
+ errors = []
259
+ details = []
260
+ passed = true
261
+
262
+ expect = test[:expect] || test['expect'] || {}
53
263
 
54
- say "Validating all tools in registry: #{config.registry}", :cyan
55
- say '', :clear
264
+ # Check exit code
265
+ expected_exit_code = expect[:exit_code] || expect['exit_code'] || 0
266
+ if result[:exit_code] != expected_exit_code
267
+ errors << "Exit code mismatch: expected #{expected_exit_code}, got #{result[:exit_code]}"
268
+ passed = false
269
+ else
270
+ details << "Exit code: #{result[:exit_code]} (as expected)"
271
+ end
272
+
273
+ # Check output_match regex
274
+ output_match = expect[:output_match] || expect['output_match']
275
+ if output_match
276
+ regex = Regexp.new(output_match)
277
+ if result[:stdout] =~ regex
278
+ details << "Output matches pattern: #{output_match}"
279
+ else
280
+ errors << "Output does not match pattern: #{output_match}"
281
+ passed = false
282
+ end
283
+ end
284
+
285
+ # Check output_contains
286
+ output_contains = expect[:output_contains] || expect['output_contains']
287
+ if output_contains && !output_contains.empty?
288
+ output_contains.each do |str|
289
+ if result[:stdout].include?(str)
290
+ details << "Output contains: #{str}"
291
+ else
292
+ errors << "Output missing string: #{str}"
293
+ passed = false
294
+ end
295
+ end
296
+ end
297
+
298
+ # Check stderr_match regex
299
+ stderr_match = expect[:stderr_match] || expect['stderr_match']
300
+ if stderr_match
301
+ regex = Regexp.new(stderr_match)
302
+ if result[:stderr] =~ regex
303
+ details << "Stderr matches pattern: #{stderr_match}"
304
+ else
305
+ errors << "Stderr does not match pattern: #{stderr_match}"
306
+ passed = false
307
+ end
308
+ end
309
+
310
+ { passed: passed, errors: errors, details: details }
311
+ end
312
+
313
+ # Get current platform as symbol
314
+ #
315
+ # @return [Symbol] platform symbol
316
+ def current_platform_symbol
317
+ case RbConfig::CONFIG['host_os']
318
+ when /linux/i
319
+ :linux
320
+ when /darwin/i
321
+ :macos
322
+ when /mswin|mingw|cygwin/i
323
+ :windows
324
+ else
325
+ :unknown
326
+ end
327
+ end
56
328
 
57
- valid_count = 0
58
- invalid_count = 0
59
- not_found_count = 0
329
+ # Evaluate skip condition (basic implementation)
330
+ #
331
+ # @param condition [String] condition string
332
+ # @param tool [Ukiryu::Tool] tool instance
333
+ # @param profile_data [Hash] profile data
334
+ # @return [Boolean] true if condition is met
335
+ def evaluate_condition(_condition, _tool, _profile_data)
336
+ # Very basic condition evaluation - can be expanded later
337
+ # For now, just return false to not skip any tests
338
+ false
339
+ end
60
340
 
61
- results.each do |result|
62
- say "#{result.tool_name.ljust(20)}: #{result.status_message}", result.valid? ? :green : :red
341
+ # Execute a simple smoke test for a command
342
+ #
343
+ # @param tool [Ukiryu::Tool] the tool instance
344
+ # @param cmd_name [Symbol] the command name
345
+ # @return [Hash] test result with :success and :message
346
+ def execute_smoke_test(tool, cmd_name)
347
+ # Try to get help for the command - most CLI tools support --help or -h
63
348
 
64
- valid_count += 1 if result.valid?
65
- invalid_count += 1 if result.invalid? && !result.not_found?
66
- not_found_count += 1 if result.not_found?
349
+ # Try with empty arguments first, just to see if the command runs
350
+ result = tool.execute(cmd_name, {})
67
351
 
68
- # Show errors for invalid tools
69
- if result.invalid? && !result.not_found?
352
+ # If we get here, the command executed
353
+ # Check for common error patterns
354
+ if result.exit_status.zero?
355
+ { success: true, message: 'Command executed successfully' }
356
+ elsif result.stderr.include?('unrecognized') || result.stderr.include?('unknown')
357
+ { success: false, message: 'Command not recognized by tool' }
358
+ else
359
+ { success: true, message: "Command executed (exit code: #{result.exit_status})" }
360
+ end
361
+ rescue Ukiryu::ExecutionError => e
362
+ if e.result.stderr.include?('unrecognized') || e.result.stderr.include?('unknown')
363
+ { success: false, message: 'Command not recognized by tool' }
364
+ else
365
+ { success: false, message: e.message }
366
+ end
367
+ end
368
+
369
+ # Validate all definitions in register
370
+ def validate_all
371
+ require_relative '../register_auto_manager'
372
+
373
+ register_path = Ukiryu::RegisterAutoManager.register_path
374
+ return say_error("Register not found: #{register_path}") unless Dir.exist?(register_path)
375
+
376
+ tools_dir = File.join(register_path, 'tools')
377
+ return say_error("Tools directory not found: #{tools_dir}") unless Dir.exist?(tools_dir)
378
+
379
+ results = {}
380
+ yaml_files = Dir.glob(File.join(tools_dir, '*', '*.yaml')).sort
381
+
382
+ say "Validating #{yaml_files.length} tool definitions...", :cyan
383
+ say '', :clear
384
+
385
+ yaml_files.each do |file|
386
+ # Get relative path from tools directory
387
+ relative_path = file.sub("#{tools_dir}/", '')
388
+
389
+ result = Ukiryu::Definition::DefinitionValidator.validate_file(
390
+ file,
391
+ schema_path: options[:schema]
392
+ )
393
+ results[file] = result
394
+
395
+ # Show file and result on same line
396
+ if result.valid?
397
+ status = result.has_warnings? ? '✓ VALID (with warnings)' : '✓ VALID'
398
+ say " #{relative_path.ljust(35)} #{status}", result.has_warnings? ? :yellow : :green
399
+ else
400
+ say " #{relative_path.ljust(35)} ✗ INVALID", :red
70
401
  result.errors.each do |error|
71
- say " - #{error}", :dim
402
+ say " - #{error}", :red
72
403
  end
73
404
  end
405
+
406
+ next unless result.has_warnings?
407
+
408
+ result.warnings.each do |warning|
409
+ say " ⚠ #{warning}", :yellow
410
+ end
74
411
  end
75
412
 
413
+ # Summary
76
414
  say '', :clear
77
- say "Summary:", :cyan
78
- say " Valid: #{valid_count}", :green
79
- say " Invalid: #{invalid_count}", invalid_count > 0 ? :red : :white
80
- say " Missing: #{not_found_count}", not_found_count > 0 ? :yellow : :white
415
+ total = results.length
416
+ valid = results.values.count(&:valid?)
417
+ invalid = results.values.count(&:invalid?)
81
418
 
82
- # Exit with error code if any validations failed
83
- exit(invalid_count > 0 ? 1 : 0)
419
+ say 'Validation Summary:', :cyan
420
+ say " Total: #{total}", :white
421
+ say " Valid: #{valid}", :green
422
+ say " Invalid: #{invalid}", invalid.zero? ? :green : :red
423
+
424
+ exit 1 if invalid.positive?
425
+ end
426
+
427
+ # Validate a YAML string
428
+ #
429
+ # @param yaml_string [String] YAML content
430
+ def validate_string(yaml_string)
431
+ result = Ukiryu::Definition::DefinitionValidator.validate_string(
432
+ yaml_string,
433
+ schema_path: options[:schema]
434
+ )
435
+
436
+ output_result(result, '<string>')
437
+ exit 1 if result.invalid? || (result.has_warnings? && options[:strict])
438
+ end
439
+
440
+ # Output validation result
441
+ #
442
+ # @param result [ValidationResult] validation result
443
+ # @param source [String] source identifier
444
+ def output_result(result, source)
445
+ case options[:format]
446
+ when 'json'
447
+ say result.to_json, :white
448
+ else
449
+ output_text(result, source)
450
+ end
451
+ end
452
+
453
+ # Output as text
454
+ #
455
+ # @param result [ValidationResult] validation result
456
+ # @param source [String] source identifier
457
+ def output_text(result, source)
458
+ say "Validating: #{source}", :cyan
459
+
460
+ if result.valid?
461
+ if result.has_warnings?
462
+ say '✓ Valid with warnings', :yellow
463
+ say '', :clear
464
+ result.warnings.each { |w| say " Warning: #{w}", :yellow }
465
+ else
466
+ say '✓ Valid', :green
467
+ end
468
+ else
469
+ say '✗ Invalid', :red
470
+ say '', :clear
471
+ result.errors.each { |e| say " Error: #{e}", :red }
472
+ if result.has_warnings?
473
+ say '', :clear
474
+ result.warnings.each { |w| say " Warning: #{w}", :yellow }
475
+ end
476
+ end
477
+ end
478
+
479
+ # Show error message
480
+ #
481
+ # @param message [String] error message
482
+ def say_error(message)
483
+ say message, :red
484
+ exit 1
84
485
  end
85
486
  end
86
487
  end
@@ -13,7 +13,7 @@ module Ukiryu
13
13
  #
14
14
  # @param identifier [String] the tool name, interface, or alias
15
15
  def run(identifier)
16
- setup_registry
16
+ setup_register
17
17
 
18
18
  runtime = Runtime.instance
19
19
  platform = options[:platform] || runtime.platform
@@ -34,7 +34,7 @@ module Ukiryu
34
34
  if tool
35
35
  show_selected_tool(tool, identifier, platform, shell)
36
36
  else
37
- error! "No tool found for: #{identifier}\nAvailable tools: #{Registry.tools.sort.join(', ')}"
37
+ error! "No tool found for: #{identifier}\nAvailable tools: #{Register.tools.sort.join(', ')}"
38
38
  end
39
39
  end
40
40
 
@@ -61,12 +61,12 @@ module Ukiryu
61
61
  # @param shell [Symbol] the shell
62
62
  # @return [Tool, nil] the tool or nil if not found
63
63
  def try_interface_discovery(identifier, platform, shell)
64
- require_relative '../registry'
64
+ require_relative '../register'
65
65
 
66
66
  candidates = []
67
67
 
68
- Registry.tools.each do |tool_name|
69
- tool_metadata = Registry.load_tool_metadata(tool_name.to_sym)
68
+ Register.tools.each do |tool_name|
69
+ tool_metadata = Register.load_tool_metadata(tool_name.to_sym)
70
70
  next unless tool_metadata
71
71
 
72
72
  # Check for interface match