aidp 0.49.12 → 0.49.13

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: 7aebc32c94a99c769c32718c0d1831888e9d7e9e16eb2968f64e9fd8e1d04b23
4
- data.tar.gz: 10380f1bfa01c5154d3aa19be32a67b8e757a66c27b1e8bb128769787357d194
3
+ metadata.gz: 5840a5dd1754c05dcb6fb0c8d2b96fecf2a093896caf19704d80397ad97f829c
4
+ data.tar.gz: '008354478b95ae1e16b8064ae0485c44aa930edec611e0b0aaa1a04da18ccaca'
5
5
  SHA512:
6
- metadata.gz: e5b4b81a0662bbc8e5d6b2a4ecdbc0104f14cef44898cd01c219ed1a09ae53218afa89cb5178acc2a4361eee3d0ff1081fd32cdf225a4df3862168081b3140d5
7
- data.tar.gz: '02699b7be327242d960f268f972b6fd38984d251878bca63b2075dceeae3383ac3ca82a42f182755da16263754e5a701581a5d6eeb3436980df7ff4c1c85384f'
6
+ metadata.gz: 0a3ca1a4fc8923f0599a85ea630a0d39e429790c1a718891e40e5e56d4d5a9170c72e03bf2d083567686e168391897dc15b8a3e4fc28c83569cb6aa0b2f7bac7
7
+ data.tar.gz: beb27cca39345af8ed6f546bd284ab1a90dd0b52336479cfe4399a1166ba70aa826f4108556ddbc33522de2a40033dff9791652a7d253043de4a5741cd441114
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "../shell_executor"
4
5
  require_relative "filter_definition"
5
6
  require_relative "provider_factory"
6
7
  require_relative "thinking_depth_manager"
@@ -264,13 +265,11 @@ module Aidp
264
265
  end
265
266
 
266
267
  def capture_sample_output(command, project_dir)
267
- # Run command and capture output (with timeout)
268
- require "open3"
269
-
270
- stdout, stderr, _ = Open3.capture3(command, chdir: project_dir)
268
+ # Run command without shell interpretation and capture output
269
+ result = ShellExecutor.new.run_line(command, chdir: project_dir)
271
270
 
272
271
  # Combine stdout and stderr for analysis
273
- output = stdout + stderr
272
+ output = result.stdout + result.stderr
274
273
  output.empty? ? nil : output
275
274
  rescue => e
276
275
  Aidp.log_debug("ai_filter_factory", "Failed to capture sample output",
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "open3"
4
3
  require_relative "../tooling_detector"
4
+ require_relative "../shell_executor"
5
5
  require_relative "output_filter"
6
6
  require_relative "output_filter_config"
7
7
  require_relative "rspec_command_optimizer"
@@ -267,16 +267,16 @@ module Aidp
267
267
  end
268
268
  end
269
269
 
270
- stdout, stderr, status = Open3.capture3(actual_command, chdir: @project_dir)
270
+ stdout, stderr, result = execute_command_line(actual_command)
271
271
 
272
272
  # Handle special case: --only-failures with no failures to rerun
273
273
  # RSpec exits with 0 and says "All examples were filtered out"
274
- if optimization_info&.dig(:optimized) && status.success? && stdout.include?("All examples were filtered out")
274
+ if optimization_info&.dig(:optimized) && result.success? && stdout.include?("All examples were filtered out")
275
275
  Aidp.log_info("test_runner", "rspec_only_failures_empty",
276
276
  message: "No failures to rerun, running full suite")
277
277
 
278
278
  # Fall back to full RSpec run
279
- stdout, stderr, status = Open3.capture3(command, chdir: @project_dir)
279
+ stdout, stderr, result = execute_command_line(command)
280
280
  actual_command = command
281
281
  end
282
282
 
@@ -284,14 +284,32 @@ module Aidp
284
284
  command: actual_command,
285
285
  original_command: command,
286
286
  type: type,
287
- success: status.success?,
288
- exit_code: status.exitstatus,
287
+ success: result.success?,
288
+ exit_code: result.exit_status,
289
289
  stdout: stdout,
290
290
  stderr: stderr,
291
291
  rspec_optimized: optimization_info&.dig(:optimized) || false
292
292
  }
293
293
  end
294
294
 
295
+ # Execute a configured command line without shell interpretation so
296
+ # shell metacharacters in configuration values are treated as data.
297
+ # Execution failures (missing executables, rejected shell operators)
298
+ # are reported as a failed command rather than raised, so a bad
299
+ # aidp.yml entry surfaces as a failed check instead of crashing the
300
+ # work loop.
301
+ def execute_command_line(command)
302
+ result = command_runner.run_line(command, chdir: @project_dir)
303
+ [result.stdout, result.stderr, result]
304
+ rescue ArgumentError, Errno::ENOENT => e
305
+ result = ShellExecutor::Result.new(stdout: "", stderr: e.message, exit_status: 1)
306
+ [result.stdout, result.stderr, result]
307
+ end
308
+
309
+ def command_runner
310
+ @command_runner ||= ShellExecutor.new
311
+ end
312
+
295
313
  def aggregate_results(results, category, mode: :full)
296
314
  # Separate required and optional command failures
297
315
  all_failures = results.reject { |r| r[:success] }
@@ -67,6 +67,38 @@ module Aidp
67
67
  Result.new(stdout: stdout, stderr: stderr, exit_status: status.exitstatus)
68
68
  end
69
69
 
70
+ # Run a command line without shell interpretation and capture its output
71
+ #
72
+ # The line is tokenized with Shellwords, so quoting in the command
73
+ # string is honored as argument boundaries, but the resulting argv is
74
+ # passed directly to the operating system. Shell metacharacters
75
+ # (operators, redirections, substitutions) are never interpreted,
76
+ # which makes this safe for command lines built from untrusted input.
77
+ #
78
+ # @param command [String] the command line to run
79
+ # @param opts [Hash] options forwarded to Open3.capture3 (e.g. chdir:)
80
+ # @return [Result] captured stdout/stderr with the exit status
81
+ # @raise [ArgumentError] when the command line is blank, has
82
+ # unbalanced quotes, or uses shell operators/redirections
83
+ def run_line(command, **opts)
84
+ require "open3"
85
+ require "shellwords"
86
+ argv = Shellwords.split(command.to_s)
87
+ raise ArgumentError, "command must not be blank" if argv.empty?
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?(/^[<>&]/) }
93
+ raise ArgumentError,
94
+ "shell operators are not supported in configured commands; " \
95
+ "split into separate commands instead: #{command.inspect}"
96
+ end
97
+
98
+ stdout, stderr, status = Open3.capture3(*argv, **opts)
99
+ Result.new(stdout: stdout, stderr: stderr, exit_status: status.exitstatus)
100
+ end
101
+
70
102
  # Run a command via system(), optionally suppressing output
71
103
  #
72
104
  # When suppress_output is true, output is redirected to /dev/null
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.12"
4
+ VERSION = "0.49.13"
5
5
  end
@@ -8,6 +8,7 @@ require_relative "../message_display"
8
8
  require_relative "../execute/prompt_manager"
9
9
  require_relative "../harness/runner"
10
10
  require_relative "../harness/state_manager"
11
+ require_relative "../shell_executor"
11
12
  require_relative "../worktree"
12
13
  require_relative "../execute/progress"
13
14
  require_relative "github_state_extractor"
@@ -1125,8 +1126,7 @@ module Aidp
1125
1126
  next if command.nil? || command.empty?
1126
1127
 
1127
1128
  Aidp.log_debug("build_processor", "quick_validation_running_test", command: command)
1128
- _stdout, _stderr, status = Open3.capture3(command)
1129
- unless status.success?
1129
+ unless quick_validation_runner.run_line(command).success?
1130
1130
  Aidp.log_debug("build_processor", "quick_validation_test_failed", command: command)
1131
1131
  return false
1132
1132
  end
@@ -1141,8 +1141,7 @@ module Aidp
1141
1141
  next if command.nil? || command.empty?
1142
1142
 
1143
1143
  Aidp.log_debug("build_processor", "quick_validation_running_lint", command: command)
1144
- _stdout, _stderr, status = Open3.capture3(command)
1145
- unless status.success?
1144
+ unless quick_validation_runner.run_line(command).success?
1146
1145
  Aidp.log_debug("build_processor", "quick_validation_lint_failed", command: command)
1147
1146
  return false
1148
1147
  end
@@ -1155,6 +1154,12 @@ module Aidp
1155
1154
  Aidp.log_warn("build_processor", "quick_validation_error", error: e.message)
1156
1155
  false
1157
1156
  end
1157
+
1158
+ # Commands come from project configuration, so run them through a
1159
+ # shell-free executor that treats metacharacters as literal data
1160
+ def quick_validation_runner
1161
+ @quick_validation_runner ||= ShellExecutor.new
1162
+ end
1158
1163
  end
1159
1164
  end
1160
1165
  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.12
4
+ version: 0.49.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bart Agapinan