aidp 0.49.9 → 0.49.11
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/lib/aidp/cli/security_command.rb +8 -7
- data/lib/aidp/execute/checkpoint.rb +3 -1
- data/lib/aidp/execute/interactive_repl.rb +4 -3
- data/lib/aidp/execute/work_loop_runner.rb +4 -2
- data/lib/aidp/harness/completion_checker.rb +9 -9
- data/lib/aidp/init/runner.rb +2 -2
- data/lib/aidp/pr_worktree_manager.rb +42 -29
- data/lib/aidp/shell_executor.rb +39 -12
- data/lib/aidp/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2521930bf3c6ad6ed74527be97a75ada7645500392b018335b3063f90e045885
|
|
4
|
+
data.tar.gz: 332b8f38ea7282420195209d115eb066311f92fac1d481b74fb8c87304df136e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f33ce70e9679f0844e008b03856da81f6de8bc0a7820eeedb25d47d5f72b93e0274d03143bd31980348ca4bd08fd0a1ee2888fb86a18a0ace1be47e1b2f322e
|
|
7
|
+
data.tar.gz: 7ea709e5742560ee4fa8617c1bdbec13143661c67e32417314a4efce07359566c188ae723ae9cc9f597a5de0977b9734077b88a72a5ac3d5578debcfbb20be9f
|
|
@@ -342,12 +342,13 @@ module Aidp
|
|
|
342
342
|
return 0
|
|
343
343
|
end
|
|
344
344
|
|
|
345
|
-
# Run RSpec with timeout protection
|
|
346
|
-
|
|
347
|
-
|
|
345
|
+
# Run RSpec with timeout protection, passing arguments without shell
|
|
346
|
+
# interpretation of the library-provided project path
|
|
347
|
+
command_args = ["bundle", "exec", "rspec", rspec_path, "--format", "failures"]
|
|
348
|
+
@prompt.say("$ #{command_args.join(" ")}\n")
|
|
348
349
|
@prompt.say("(timeout: #{AUDIT_TIMEOUT_SECONDS / 60} minutes)\n")
|
|
349
350
|
|
|
350
|
-
exit_status = run_with_timeout(
|
|
351
|
+
exit_status = run_with_timeout(command_args, AUDIT_TIMEOUT_SECONDS)
|
|
351
352
|
|
|
352
353
|
case exit_status
|
|
353
354
|
when 0
|
|
@@ -365,11 +366,11 @@ module Aidp
|
|
|
365
366
|
private
|
|
366
367
|
|
|
367
368
|
# Run a command with timeout protection
|
|
368
|
-
# @param
|
|
369
|
+
# @param command_args [Array<String>] The command and its arguments
|
|
369
370
|
# @param timeout [Integer] Timeout in seconds
|
|
370
371
|
# @return [Integer, Symbol] Exit status or :timeout
|
|
371
|
-
def run_with_timeout(
|
|
372
|
-
pid = spawn(
|
|
372
|
+
def run_with_timeout(command_args, timeout)
|
|
373
|
+
pid = Process.spawn(*command_args)
|
|
373
374
|
start_time = Time.now
|
|
374
375
|
|
|
375
376
|
loop do
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
require "time"
|
|
5
5
|
require "json"
|
|
6
|
+
require "open3"
|
|
6
7
|
require "aidp/rescue_logging"
|
|
7
8
|
|
|
8
9
|
module Aidp
|
|
@@ -183,7 +184,8 @@ module Aidp
|
|
|
183
184
|
def run_rubocop_check
|
|
184
185
|
# Run rubocop and parse output to get a quality score
|
|
185
186
|
# This is a simplified version - could be enhanced
|
|
186
|
-
|
|
187
|
+
stdout, _stderr, _status = Open3.capture3("rubocop", "--format", "json", chdir: @project_dir)
|
|
188
|
+
result = stdout
|
|
187
189
|
return nil if result.empty?
|
|
188
190
|
|
|
189
191
|
data = JSON.parse(result)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "tty-prompt"
|
|
4
4
|
require "tty-spinner"
|
|
5
|
+
require "open3"
|
|
5
6
|
require_relative "async_work_loop_runner"
|
|
6
7
|
require_relative "repl_macros"
|
|
7
8
|
require_relative "../rescue_logging"
|
|
@@ -307,9 +308,9 @@ module Aidp
|
|
|
307
308
|
}
|
|
308
309
|
end
|
|
309
310
|
|
|
310
|
-
# Execute reset
|
|
311
|
-
output =
|
|
312
|
-
success =
|
|
311
|
+
# Execute reset without shell interpretation of the rollback count
|
|
312
|
+
output, status = Open3.capture2e("git", "reset", "--hard", "HEAD~#{count}")
|
|
313
|
+
success = status.success?
|
|
313
314
|
|
|
314
315
|
{
|
|
315
316
|
success: success,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
3
4
|
require_relative "prompt_manager"
|
|
4
5
|
require_relative "prompt_evaluator"
|
|
5
6
|
require_relative "checkpoint"
|
|
@@ -2359,8 +2360,9 @@ module Aidp
|
|
|
2359
2360
|
|
|
2360
2361
|
stats = {}
|
|
2361
2362
|
files.each do |file|
|
|
2362
|
-
# Use git diff to get line counts
|
|
2363
|
-
|
|
2363
|
+
# Use git diff to get line counts without shell interpretation of file names
|
|
2364
|
+
stdout, _stderr, _status = Open3.capture3("git", "diff", "--numstat", "HEAD", "--", file.to_s)
|
|
2365
|
+
output = stdout.strip
|
|
2364
2366
|
next if output.empty?
|
|
2365
2367
|
|
|
2366
2368
|
parts = output.split("\t")
|
|
@@ -64,27 +64,27 @@ module Aidp
|
|
|
64
64
|
test_commands = detect_test_commands
|
|
65
65
|
return true if test_commands.empty?
|
|
66
66
|
|
|
67
|
-
test_commands.any?
|
|
68
|
-
system("cd #{@project_dir} && #{cmd} > /dev/null 2>&1")
|
|
69
|
-
end
|
|
67
|
+
test_commands.any? { |cmd| run_project_command(cmd) }
|
|
70
68
|
end
|
|
71
69
|
|
|
72
70
|
def linting_clean?
|
|
73
71
|
lint_commands = detect_lint_commands
|
|
74
72
|
return true if lint_commands.empty?
|
|
75
73
|
|
|
76
|
-
lint_commands.all?
|
|
77
|
-
system("cd #{@project_dir} && #{cmd} > /dev/null 2>&1")
|
|
78
|
-
end
|
|
74
|
+
lint_commands.all? { |cmd| run_project_command(cmd) }
|
|
79
75
|
end
|
|
80
76
|
|
|
81
77
|
def build_successful?
|
|
82
78
|
build_commands = detect_build_commands
|
|
83
79
|
return true if build_commands.empty?
|
|
84
80
|
|
|
85
|
-
build_commands.any?
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
build_commands.any? { |cmd| run_project_command(cmd) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Run a detected command in the project directory without building a
|
|
85
|
+
# shell command string from the library-provided project path
|
|
86
|
+
def run_project_command(cmd)
|
|
87
|
+
system(cmd, chdir: @project_dir, out: File::NULL, err: File::NULL)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def documentation_complete?
|
data/lib/aidp/init/runner.rb
CHANGED
|
@@ -357,9 +357,9 @@ module Aidp
|
|
|
357
357
|
|
|
358
358
|
next unless tool_command
|
|
359
359
|
|
|
360
|
-
# Check if command exists
|
|
360
|
+
# Check if command exists without shell interpretation
|
|
361
361
|
begin
|
|
362
|
-
unless system("which
|
|
362
|
+
unless system("which", tool_command, out: File::NULL, err: File::NULL)
|
|
363
363
|
warnings << " ⚠️ #{format_tool(tool_data[:tool])} detected but command '#{tool_command}' not found in PATH"
|
|
364
364
|
end
|
|
365
365
|
rescue IOError
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "fileutils"
|
|
3
|
-
require "shellwords"
|
|
4
3
|
|
|
5
4
|
module Aidp
|
|
6
5
|
# Manages worktrees specifically for Pull Request branches
|
|
@@ -151,17 +150,18 @@ module Aidp
|
|
|
151
150
|
branch == "* #{base_branch}"
|
|
152
151
|
end
|
|
153
152
|
|
|
154
|
-
# Enhance branch tracking and fetching
|
|
153
|
+
# Enhance branch tracking and fetching; argv execution keeps the
|
|
154
|
+
# library-provided branch name from being interpreted by a shell
|
|
155
155
|
unless base_branch_exists
|
|
156
156
|
# Try multiple fetch strategies
|
|
157
157
|
fetch_commands = [
|
|
158
|
-
"git fetch origin #{base_branch}:#{base_branch}
|
|
159
|
-
"git fetch origin
|
|
160
|
-
"git fetch --all
|
|
158
|
+
["git", "fetch", "origin", "#{base_branch}:#{base_branch}"],
|
|
159
|
+
["git", "fetch", "origin"],
|
|
160
|
+
["git", "fetch", "--all"]
|
|
161
161
|
]
|
|
162
162
|
|
|
163
|
-
fetch_commands.each do |
|
|
164
|
-
@shell_executor.system(
|
|
163
|
+
fetch_commands.each do |fetch_args|
|
|
164
|
+
@shell_executor.system(*fetch_args, err: File::NULL)
|
|
165
165
|
branch_list_output = `git branch -a`.split("\n").map(&:strip)
|
|
166
166
|
base_branch_exists = branch_list_output.any? do |branch|
|
|
167
167
|
branch.end_with?("/#{base_branch}", "remotes/origin/#{base_branch}") ||
|
|
@@ -174,14 +174,15 @@ module Aidp
|
|
|
174
174
|
|
|
175
175
|
raise ArgumentError, "Base branch '#{base_branch}' does not exist in the repository" unless base_branch_exists
|
|
176
176
|
|
|
177
|
-
# Robust worktree creation with enhanced error handling and logging
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
# Robust worktree creation with enhanced error handling and logging;
|
|
178
|
+
# argv execution keeps library-provided branches and paths literal
|
|
179
|
+
worktree_create_args = ["git", "worktree", "add", worktree_path, "-b", head_branch, base_branch]
|
|
180
|
+
unless @shell_executor.system(*worktree_create_args)
|
|
180
181
|
error_details = {
|
|
181
182
|
pr_number: pr_number,
|
|
182
183
|
base_branch: base_branch,
|
|
183
184
|
head_branch: head_branch,
|
|
184
|
-
command:
|
|
185
|
+
command: worktree_create_args.join(" ")
|
|
185
186
|
}
|
|
186
187
|
Aidp.log_error(
|
|
187
188
|
"pr_worktree_manager", "worktree_creation_failed",
|
|
@@ -377,9 +378,9 @@ module Aidp
|
|
|
377
378
|
end
|
|
378
379
|
end
|
|
379
380
|
|
|
380
|
-
# Stage only successfully modified files
|
|
381
|
+
# Stage only successfully modified files without shell interpretation
|
|
381
382
|
unless successful_files.empty?
|
|
382
|
-
@shell_executor.system("git add
|
|
383
|
+
@shell_executor.system("git", "add", "--", *successful_files)
|
|
383
384
|
end
|
|
384
385
|
end
|
|
385
386
|
|
|
@@ -428,25 +429,22 @@ module Aidp
|
|
|
428
429
|
|
|
429
430
|
Dir.chdir(worktree_path) do
|
|
430
431
|
# Check staged changes with more robust capture
|
|
431
|
-
staged_changes_output = @shell_executor.
|
|
432
|
+
staged_changes_output = @shell_executor.run_argv("git", "diff", "--staged", "--name-only").stdout.strip
|
|
432
433
|
|
|
433
434
|
if !staged_changes_output.empty?
|
|
434
435
|
push_result[:git_actions][:staged_changes] = true
|
|
435
436
|
push_result[:changed_files] = staged_changes_output.split("\n")
|
|
436
437
|
|
|
437
|
-
#
|
|
438
|
-
|
|
439
|
-
commit_command = "git commit -m '#{commit_message}' 2>&1"
|
|
440
|
-
commit_output = @shell_executor.run(commit_command).strip
|
|
438
|
+
# Commit staged changes without shell interpretation of library input
|
|
439
|
+
commit_output = commit_staged_changes(pr_number)
|
|
441
440
|
|
|
442
|
-
if
|
|
441
|
+
if commit_output.success?
|
|
443
442
|
push_result[:git_actions][:committed] = true
|
|
444
443
|
|
|
445
|
-
#
|
|
446
|
-
|
|
447
|
-
push_output = @shell_executor.run(push_command).strip
|
|
444
|
+
# Push the registry-sourced head branch without shell interpretation
|
|
445
|
+
push_output = push_head_branch(head_branch)
|
|
448
446
|
|
|
449
|
-
if
|
|
447
|
+
if push_output.success?
|
|
450
448
|
push_result[:git_actions][:pushed] = true
|
|
451
449
|
push_result[:success] = true
|
|
452
450
|
|
|
@@ -458,22 +456,22 @@ module Aidp
|
|
|
458
456
|
)
|
|
459
457
|
else
|
|
460
458
|
# Detailed push error logging
|
|
461
|
-
push_result[:errors] << "Push failed: #{push_output}"
|
|
459
|
+
push_result[:errors] << "Push failed: #{push_output.output.strip}"
|
|
462
460
|
Aidp.log_error(
|
|
463
461
|
"pr_worktree_manager", "push_changes_failed",
|
|
464
462
|
pr_number: pr_number,
|
|
465
463
|
branch: head_branch,
|
|
466
|
-
error_details: push_output
|
|
464
|
+
error_details: push_output.output.strip
|
|
467
465
|
)
|
|
468
466
|
end
|
|
469
467
|
else
|
|
470
468
|
# Detailed commit error logging
|
|
471
|
-
push_result[:errors] << "Commit failed: #{commit_output}"
|
|
469
|
+
push_result[:errors] << "Commit failed: #{commit_output.output.strip}"
|
|
472
470
|
Aidp.log_error(
|
|
473
471
|
"pr_worktree_manager", "commit_changes_failed",
|
|
474
472
|
pr_number: pr_number,
|
|
475
473
|
branch: head_branch,
|
|
476
|
-
error_details: commit_output
|
|
474
|
+
error_details: commit_output.output.strip
|
|
477
475
|
)
|
|
478
476
|
end
|
|
479
477
|
else
|
|
@@ -496,8 +494,10 @@ module Aidp
|
|
|
496
494
|
existing_worktree = @worktrees[pr_number.to_s]
|
|
497
495
|
return false unless existing_worktree
|
|
498
496
|
|
|
499
|
-
# Remove git worktree
|
|
500
|
-
|
|
497
|
+
# Remove git worktree without shell interpretation of registry data
|
|
498
|
+
if File.exist?(existing_worktree["path"])
|
|
499
|
+
@shell_executor.system("git", "worktree", "remove", existing_worktree["path"])
|
|
500
|
+
end
|
|
501
501
|
|
|
502
502
|
# Remove from registry and save
|
|
503
503
|
@worktrees.delete(pr_number.to_s)
|
|
@@ -532,6 +532,19 @@ module Aidp
|
|
|
532
532
|
|
|
533
533
|
private
|
|
534
534
|
|
|
535
|
+
# Commit currently staged changes; argv execution keeps externally
|
|
536
|
+
# sourced values (pr_number) from being interpreted by a shell
|
|
537
|
+
def commit_staged_changes(pr_number)
|
|
538
|
+
commit_message = "Changes applied via AIDP request-changes workflow for PR ##{pr_number}"
|
|
539
|
+
@shell_executor.run_argv("git", "commit", "-m", commit_message)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Push the head branch; argv execution keeps registry-sourced branch
|
|
543
|
+
# names from being interpreted by a shell
|
|
544
|
+
def push_head_branch(head_branch)
|
|
545
|
+
@shell_executor.run_argv("git", "push", "origin", "--", head_branch)
|
|
546
|
+
end
|
|
547
|
+
|
|
535
548
|
# Load the worktree registry from file
|
|
536
549
|
def load_registry
|
|
537
550
|
return {} unless File.exist?(worktree_registry_path)
|
data/lib/aidp/shell_executor.rb
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Aidp
|
|
4
4
|
# Shell command executor wrapper for testability
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Commands are always executed in argument form so shell
|
|
7
|
+
# metacharacters in any value are treated as literal data:
|
|
8
|
+
# 1. `run_argv(*command)` - Captures output without shell interpretation
|
|
8
9
|
# 2. `system(*args)` - Wraps Kernel.system() with optional output suppression
|
|
9
10
|
#
|
|
10
11
|
# In tests, set `ShellExecutor.suppress_output = true` to suppress all
|
|
@@ -25,19 +26,45 @@ module Aidp
|
|
|
25
26
|
end
|
|
26
27
|
self.suppress_output = false
|
|
27
28
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
# Immutable result of a shell-free command execution
|
|
30
|
+
class Result
|
|
31
|
+
attr_reader :stdout, :stderr, :exit_status
|
|
32
|
+
|
|
33
|
+
# @param stdout [String] standard output from the command
|
|
34
|
+
# @param stderr [String] standard error from the command
|
|
35
|
+
# @param exit_status [Integer] exit status code
|
|
36
|
+
def initialize(stdout:, stderr:, exit_status:)
|
|
37
|
+
@stdout = stdout.to_s.freeze
|
|
38
|
+
@stderr = stderr.to_s.freeze
|
|
39
|
+
@exit_status = exit_status || 1
|
|
40
|
+
freeze
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Boolean] true if exit_status is 0
|
|
44
|
+
def success?
|
|
45
|
+
@exit_status.zero?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Combined output, equivalent to shell `2>&1` redirection
|
|
49
|
+
# @return [String]
|
|
50
|
+
def output
|
|
51
|
+
[@stdout, @stderr].reject(&:empty?).join("\n")
|
|
52
|
+
end
|
|
34
53
|
end
|
|
35
54
|
|
|
36
|
-
#
|
|
55
|
+
# Run a command without shell interpretation and capture its output
|
|
56
|
+
#
|
|
57
|
+
# The command and its arguments are passed directly to the operating
|
|
58
|
+
# system, so shell metacharacters in any argument are treated as
|
|
59
|
+
# literal data. Prefer this over #run whenever an argument may contain
|
|
60
|
+
# externally supplied values.
|
|
37
61
|
#
|
|
38
|
-
# @
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
# @param command [Array<String>] the command and its arguments
|
|
63
|
+
# @return [Result] captured stdout/stderr with the exit status
|
|
64
|
+
def run_argv(*command)
|
|
65
|
+
require "open3"
|
|
66
|
+
stdout, stderr, status = Open3.capture3(*command.map(&:to_s))
|
|
67
|
+
Result.new(stdout: stdout, stderr: stderr, exit_status: status.exitstatus)
|
|
41
68
|
end
|
|
42
69
|
|
|
43
70
|
# Run a command via system(), optionally suppressing output
|
data/lib/aidp/version.rb
CHANGED