aidp 0.49.13 → 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: 5840a5dd1754c05dcb6fb0c8d2b96fecf2a093896caf19704d80397ad97f829c
4
- data.tar.gz: '008354478b95ae1e16b8064ae0485c44aa930edec611e0b0aaa1a04da18ccaca'
3
+ metadata.gz: '0088a7c834709e32224adf25831bfd282f1f9a8019261b9055251699d8b5b99a'
4
+ data.tar.gz: b00f8596f8cc68cc26f13553a1f1e1c37f0e1d3a6fb7b21dc79ebbc704fede23
5
5
  SHA512:
6
- metadata.gz: 0a3ca1a4fc8923f0599a85ea630a0d39e429790c1a718891e40e5e56d4d5a9170c72e03bf2d083567686e168391897dc15b8a3e4fc28c83569cb6aa0b2f7bac7
7
- data.tar.gz: beb27cca39345af8ed6f546bd284ab1a90dd0b52336479cfe4399a1166ba70aa826f4108556ddbc33522de2a40033dff9791652a7d253043de4a5741cd441114
6
+ metadata.gz: dd15f595a34f2d6258eb0c62d787c0bdc78b18f69d09ea9cf7db26846badede64dc469ff9d3adc9a8a99c13d6bf2b3a6ca794744953883000331b380a7bd1de4
7
+ data.tar.gz: b3d0e0710c139d6a509cc7aaf1b07d205b3c1a35b38c61339fa6b83e3ca5ca14708547a7d0af32cf069841bdc7ded1cb493366ab1f672895056e21452e04dec3
@@ -77,7 +77,7 @@ module Aidp
77
77
  return nil unless File.exist?(".git")
78
78
 
79
79
  # Try to get origin URL
80
- stdout, _stderr, status = Open3.capture3("git remote get-url origin")
80
+ stdout, _stderr, status = Open3.capture3("git", "remote", "get-url", "origin")
81
81
  return nil unless status.success?
82
82
 
83
83
  origin_url = stdout.strip
@@ -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],
@@ -1343,7 +1343,7 @@ module Aidp
1343
1343
  prompt.say("\nšŸ”€ Pull Request Configuration")
1344
1344
 
1345
1345
  # Check if remote exists
1346
- has_remote = system("git remote -v > /dev/null 2>&1")
1346
+ has_remote = system("git", "remote", "-v", out: File::NULL, err: File::NULL)
1347
1347
 
1348
1348
  unless has_remote
1349
1349
  prompt.say("No git remote detected. PR creation will be disabled.")
@@ -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.13"
4
+ VERSION = "0.49.15"
5
5
  end
@@ -749,7 +749,7 @@ module Aidp
749
749
  # Capture git worktree context
750
750
  # @return [Hash] Worktree information
751
751
  def capture_worktree_context
752
- return {} unless system("git rev-parse --git-dir > /dev/null 2>&1")
752
+ return {} unless system("git", "rev-parse", "--git-dir", out: File::NULL, err: File::NULL)
753
753
 
754
754
  {
755
755
  branch: `git rev-parse --abbrev-ref HEAD`.strip,
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.13
4
+ version: 0.49.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan