aidp 0.49.14 → 0.49.15

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: e0670861e0010f7d691a78995723512362bbc9bc3562c259319e4aec8ae90bec
4
- data.tar.gz: 790b9b93cb98ea2dc6c4a9c210d2943e8e78df5e6921fdd7dafdcc3bc62af309
3
+ metadata.gz: '0088a7c834709e32224adf25831bfd282f1f9a8019261b9055251699d8b5b99a'
4
+ data.tar.gz: b00f8596f8cc68cc26f13553a1f1e1c37f0e1d3a6fb7b21dc79ebbc704fede23
5
5
  SHA512:
6
- metadata.gz: 05d15e5b9a97261dff4cba04fa8b830e594553d0dd0f20a29baa757e3085c076cfe32e3889d2a19f03270483cf746c1329c7127b99ba9e7c16d63b0b81e1f862
7
- data.tar.gz: 94d6d3f9393050449c3fe2f1521acb793f09dd27d247422184374a398c6f4fc91f595d889a12b99b765d6475d1a3deb2360831b614df909463a02bdd6c9c86df
6
+ metadata.gz: dd15f595a34f2d6258eb0c62d787c0bdc78b18f69d09ea9cf7db26846badede64dc469ff9d3adc9a8a99c13d6bf2b3a6ca794744953883000331b380a7bd1de4
7
+ data.tar.gz: b3d0e0710c139d6a509cc7aaf1b07d205b3c1a35b38c61339fa6b83e3ca5ca14708547a7d0af32cf069841bdc7ded1cb493366ab1f672895056e21452e04dec3
@@ -3,6 +3,7 @@
3
3
  require "yaml"
4
4
  require_relative "../message_display"
5
5
  require_relative "../rescue_logging"
6
+ require_relative "../shell_executor"
6
7
  require_relative "../util"
7
8
 
8
9
  module Aidp
@@ -228,24 +229,22 @@ module Aidp
228
229
  end
229
230
 
230
231
  def build_default_command_runner
232
+ command_executor = ShellExecutor.new
231
233
  lambda do |command, _context|
232
- require "tty-command"
233
-
234
- cmd = TTY::Command.new(printer: :quiet)
235
- result = cmd.run(command, chdir: @project_dir)
236
-
237
- {
238
- exit_status: result.exit_status,
239
- stdout: result.out,
240
- stderr: result.err
241
- }
242
- rescue TTY::Command::ExitError => e
243
- result = e.result
234
+ result = command_executor.run_line(command, chdir: @project_dir)
244
235
  {
245
236
  exit_status: result.exit_status,
246
- stdout: result.out,
247
- stderr: result.err
237
+ stdout: result.stdout,
238
+ stderr: result.stderr
248
239
  }
240
+ rescue ArgumentError => e
241
+ # Rejected shell syntax or an unparseable command line (Shellwords
242
+ # raises ArgumentError for unbalanced quotes or NUL bytes)
243
+ {exit_status: 127, stdout: "", stderr: e.message}
244
+ rescue SystemCallError => e
245
+ # Spawn failures: missing binary (ENOENT), non-executable file
246
+ # (EACCES), directory used as command (EISDIR), and similar errnos
247
+ {exit_status: 127, stdout: "", stderr: e.message}
249
248
  end
250
249
  end
251
250
  end
@@ -301,7 +301,7 @@ module Aidp
301
301
  def execute_command_line(command)
302
302
  result = command_runner.run_line(command, chdir: @project_dir)
303
303
  [result.stdout, result.stderr, result]
304
- rescue ArgumentError, Errno::ENOENT => e
304
+ rescue ArgumentError, SystemCallError => e
305
305
  result = ShellExecutor::Result.new(stdout: "", stderr: e.message, exit_status: 1)
306
306
  [result.stdout, result.stderr, result]
307
307
  end
@@ -650,7 +650,7 @@ module Aidp
650
650
  q.validate(/\A[a-z0-9_]+\z/i, "Name must be alphanumeric with underscores")
651
651
  end
652
652
 
653
- command = prompt.ask("Shell command to run:", required: true)
653
+ command = prompt.ask("Command to run (executed without a shell; no &&, ||, |, ;, redirects, or VAR=value prefixes):", required: true)
654
654
 
655
655
  category_choices = [
656
656
  ["Test (unit, integration, e2e)", :test],
@@ -86,15 +86,33 @@ module Aidp
86
86
  argv = Shellwords.split(command.to_s)
87
87
  raise ArgumentError, "command must not be blank" if argv.empty?
88
88
 
89
- # Operator tokens signal an intent to use shell features, which are
90
- # intentionally unsupported here; reject them loudly rather than
91
- # running them as literal arguments.
92
- if argv.any? { |token| %w[&& || ; |].include?(token) || token.match?(/^[<>&]/) }
89
+ # A leading NAME=value token is a shell environment assignment; without
90
+ # a shell it would be treated as the program name and fail to run.
91
+ if argv.first.match?(/\A[A-Za-z_][A-Za-z0-9_]*=/)
92
+ raise ArgumentError,
93
+ "environment variable assignments are not supported; " \
94
+ "prefix the command with env (e.g. env VAR=value command)"
95
+ end
96
+
97
+ # Operator and redirection tokens signal an intent to use shell
98
+ # features, which are intentionally unsupported here; reject them
99
+ # loudly rather than running them as literal arguments. A leading file
100
+ # descriptor digit (e.g. `2>`) still introduces a shell redirect.
101
+ if argv.any? { |token| %w[&& || ; |].include?(token) || token.match?(/\A\d*[<>&]/) }
93
102
  raise ArgumentError,
94
103
  "shell operators are not supported in configured commands; " \
95
104
  "split into separate commands instead: #{command.inspect}"
96
105
  end
97
106
 
107
+ # A single token that still contains shell metacharacters would be
108
+ # handed to Process.spawn as a lone string, which Ruby routes through
109
+ # sh -c; reject it rather than let the shell interpret it.
110
+ if argv.length == 1 && argv.first.match?(/[*?\[\]{}()<>|;&$\\"'`\r\n~#=]/)
111
+ raise ArgumentError,
112
+ "shell metacharacters in a single-token command are not supported; " \
113
+ "split into program and arguments"
114
+ end
115
+
98
116
  stdout, stderr, status = Open3.capture3(*argv, **opts)
99
117
  Result.new(stdout: stdout, stderr: stderr, exit_status: status.exitstatus)
100
118
  end
data/lib/aidp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.49.14"
4
+ VERSION = "0.49.15"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aidp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.49.14
4
+ version: 0.49.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan