aidp 0.49.14 → 0.49.16
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/issue_importer.rb +12 -8
- data/lib/aidp/execute/deterministic_unit.rb +13 -14
- data/lib/aidp/harness/test_runner.rb +1 -1
- data/lib/aidp/setup/wizard.rb +1 -1
- data/lib/aidp/shell_executor.rb +22 -4
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/worktree.rb +8 -3
- data/lib/aidp/worktree_branch_manager.rb +14 -13
- 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: 07a0de360baac2e2fe9bef74c5640816819eb0287c33dedecc841e1796de446d
|
|
4
|
+
data.tar.gz: 2852544514c863e1ee4fa17ede88416556aef5bc427b216669a8dd2d872de864
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c91a1fb45d5f061959443f649d81af1750940bbd4f3699e16f4f8b5ee0402fdc6ff0fb3ef78a8bd555728b06c010debcce49f26a7409cfc838e1fe50e50d3941
|
|
7
|
+
data.tar.gz: b80d5ff7e95b21507a9a7949a58e5908a7cd659a3155e330210150eac63c11253b41e2d1af1c65d7eb86cd08ec580c99b42a2f8bff02c9cb1e825f7daea7db09
|
|
@@ -120,14 +120,15 @@ module Aidp
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def fetch_via_gh_cli(owner, repo, number)
|
|
123
|
-
|
|
123
|
+
# Keep the gh CLI and its flags as separate argv entries so the
|
|
124
|
+
# library-provided owner/repo values are never interpreted by a shell.
|
|
125
|
+
Aidp.log_debug(COMPONENT, "Running gh cli", owner: owner, repo: repo, number: number, command: "gh issue view")
|
|
126
|
+
stdout, stderr, status = capture3_with_timeout(
|
|
124
127
|
"gh", "issue", "view", number,
|
|
125
128
|
"--repo", "#{owner}/#{repo}",
|
|
126
|
-
"--json", "title,body,labels,milestone,comments,state,assignees,number,url"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Aidp.log_debug(COMPONENT, "Running gh cli", owner: owner, repo: repo, number: number, command: cmd.join(" "))
|
|
130
|
-
stdout, stderr, status = capture3_with_timeout(*cmd, timeout: gh_cli_timeout)
|
|
129
|
+
"--json", "title,body,labels,milestone,comments,state,assignees,number,url",
|
|
130
|
+
timeout: gh_cli_timeout
|
|
131
|
+
)
|
|
131
132
|
Aidp.log_debug(COMPONENT, "Completed gh cli", owner: owner, repo: repo, number: number, exitstatus: status.exitstatus)
|
|
132
133
|
|
|
133
134
|
unless status.success?
|
|
@@ -320,14 +321,17 @@ module Aidp
|
|
|
320
321
|
false
|
|
321
322
|
end
|
|
322
323
|
|
|
323
|
-
|
|
324
|
+
# Run a command without shell interpretation: the executable is passed as
|
|
325
|
+
# a separate leading argument, so shell metacharacters in any argument
|
|
326
|
+
# value are treated as literal data.
|
|
327
|
+
def capture3_with_timeout(command, *args, timeout:)
|
|
324
328
|
stdout_str = +""
|
|
325
329
|
stderr_str = +""
|
|
326
330
|
status = nil
|
|
327
331
|
wait_thr = nil
|
|
328
332
|
|
|
329
333
|
Timeout.timeout(timeout) do
|
|
330
|
-
Open3.popen3(*
|
|
334
|
+
Open3.popen3(command, *args) do |stdin, stdout_io, stderr_io, thread|
|
|
331
335
|
wait_thr = thread
|
|
332
336
|
stdin.close
|
|
333
337
|
stdout_str = stdout_io.read
|
|
@@ -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
|
-
|
|
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.
|
|
247
|
-
stderr: result.
|
|
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,
|
|
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
|
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -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("
|
|
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],
|
data/lib/aidp/shell_executor.rb
CHANGED
|
@@ -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
|
-
#
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
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
data/lib/aidp/worktree.rb
CHANGED
|
@@ -344,7 +344,10 @@ module Aidp
|
|
|
344
344
|
worktree_path: worktree_path,
|
|
345
345
|
base_branch: effective_base_branch
|
|
346
346
|
)
|
|
347
|
-
|
|
347
|
+
# Pass the git subcommand as a separate leading argument so the
|
|
348
|
+
# library-provided branch and path values are never interpreted
|
|
349
|
+
# by a shell.
|
|
350
|
+
stdout, stderr, status = Dir.chdir(project_dir) { Open3.capture3("git", *cmd) }
|
|
348
351
|
|
|
349
352
|
return if status.success?
|
|
350
353
|
|
|
@@ -367,11 +370,13 @@ module Aidp
|
|
|
367
370
|
end
|
|
368
371
|
end
|
|
369
372
|
|
|
373
|
+
# Build the worktree creation arguments (without the leading "git"),
|
|
374
|
+
# so the caller can pass them as separate argv entries.
|
|
370
375
|
def build_worktree_command(branch_exists:, branch:, worktree_path:, base_branch:)
|
|
371
376
|
if branch_exists
|
|
372
|
-
["
|
|
377
|
+
["worktree", "add", worktree_path, branch]
|
|
373
378
|
else
|
|
374
|
-
cmd = ["
|
|
379
|
+
cmd = ["worktree", "add", "-b", branch, worktree_path]
|
|
375
380
|
cmd << base_branch if base_branch
|
|
376
381
|
cmd
|
|
377
382
|
end
|
|
@@ -68,7 +68,7 @@ module Aidp
|
|
|
68
68
|
# 3. Use git worktree list for broader search
|
|
69
69
|
-> {
|
|
70
70
|
begin
|
|
71
|
-
worktree_list_output = run_git_command(
|
|
71
|
+
worktree_list_output = run_git_command("git", "worktree", "list")
|
|
72
72
|
worktree_list_output.split("\n").each do |line|
|
|
73
73
|
path, branch_info = line.split(" ", 2)
|
|
74
74
|
return path if branch_info&.include?(branch)
|
|
@@ -152,7 +152,7 @@ module Aidp
|
|
|
152
152
|
if force_recreate
|
|
153
153
|
# Attempt to remove existing worktree
|
|
154
154
|
begin
|
|
155
|
-
run_git_command(
|
|
155
|
+
run_git_command("git", "worktree", "remove", existing_worktree)
|
|
156
156
|
Aidp.log_debug("worktree_branch_manager", "existing_worktree_removed",
|
|
157
157
|
path: existing_worktree)
|
|
158
158
|
rescue => e
|
|
@@ -187,8 +187,8 @@ module Aidp
|
|
|
187
187
|
Aidp.log_debug("worktree_branch_manager", "create_strategy_remote_tracking",
|
|
188
188
|
branch: effective_branch,
|
|
189
189
|
base_branch: resolved_base_branch)
|
|
190
|
-
run_git_command(
|
|
191
|
-
run_git_command(
|
|
190
|
+
run_git_command("git", "fetch", "origin", resolved_base_branch)
|
|
191
|
+
run_git_command("git", "worktree", "add", "-b", effective_branch, worktree_path, "origin/#{resolved_base_branch}")
|
|
192
192
|
},
|
|
193
193
|
|
|
194
194
|
# Strategy 2: Create without remote tracking
|
|
@@ -196,7 +196,7 @@ module Aidp
|
|
|
196
196
|
Aidp.log_debug("worktree_branch_manager", "create_strategy_local_branch",
|
|
197
197
|
branch: effective_branch,
|
|
198
198
|
base_branch: resolved_base_branch)
|
|
199
|
-
run_git_command(
|
|
199
|
+
run_git_command("git", "worktree", "add", "-b", effective_branch, worktree_path, resolved_base_branch)
|
|
200
200
|
},
|
|
201
201
|
|
|
202
202
|
# Strategy 3: Checkout existing branch (for when branch already exists)
|
|
@@ -204,7 +204,7 @@ module Aidp
|
|
|
204
204
|
Aidp.log_debug("worktree_branch_manager", "create_strategy_existing_branch",
|
|
205
205
|
branch: effective_branch,
|
|
206
206
|
base_branch: resolved_base_branch)
|
|
207
|
-
run_git_command(
|
|
207
|
+
run_git_command("git", "worktree", "add", worktree_path, effective_branch)
|
|
208
208
|
}
|
|
209
209
|
]
|
|
210
210
|
|
|
@@ -317,7 +317,7 @@ module Aidp
|
|
|
317
317
|
# If PR number is provided, try to get base branch from GitHub
|
|
318
318
|
if pr_number
|
|
319
319
|
begin
|
|
320
|
-
pr_info_output = run_git_command(
|
|
320
|
+
pr_info_output = run_git_command("gh", "pr", "view", pr_number.to_s, "--json", "baseRefName")
|
|
321
321
|
pr_base_branch = JSON.parse(pr_info_output)["baseRefName"]
|
|
322
322
|
return pr_base_branch if pr_base_branch && !pr_base_branch.empty?
|
|
323
323
|
rescue => e
|
|
@@ -328,7 +328,7 @@ module Aidp
|
|
|
328
328
|
|
|
329
329
|
# Try to fetch origin branches
|
|
330
330
|
begin
|
|
331
|
-
run_git_command(
|
|
331
|
+
run_git_command("git", "fetch", "origin", default_base_branch)
|
|
332
332
|
rescue => e
|
|
333
333
|
Aidp.log_debug("worktree_branch_manager", "base_branch_fetch_failed",
|
|
334
334
|
base_branch: default_base_branch, error: e.message)
|
|
@@ -352,11 +352,12 @@ module Aidp
|
|
|
352
352
|
end
|
|
353
353
|
end
|
|
354
354
|
|
|
355
|
-
|
|
355
|
+
# Run a command with argv execution so shell metacharacters in any
|
|
356
|
+
# library-provided value (branch names, paths) are treated as literal
|
|
357
|
+
# data instead of being interpreted by a shell.
|
|
358
|
+
def run_git_command(command, *args)
|
|
356
359
|
Dir.chdir(@project_dir) do
|
|
357
|
-
|
|
358
|
-
args = cmd.is_a?(Array) ? cmd : cmd.split
|
|
359
|
-
output, status = Open3.capture2e(*args)
|
|
360
|
+
output, status = Open3.capture2e(command, *args)
|
|
360
361
|
raise StandardError, output unless status.success?
|
|
361
362
|
output
|
|
362
363
|
end
|
|
@@ -444,7 +445,7 @@ module Aidp
|
|
|
444
445
|
|
|
445
446
|
# Fetch pull request information from GitHub
|
|
446
447
|
begin
|
|
447
|
-
pr_info_output = run_git_command(
|
|
448
|
+
pr_info_output = run_git_command("gh", "pr", "view", pr_number.to_s, "--json", "headRefName")
|
|
448
449
|
pr_branch = JSON.parse(pr_info_output)["headRefName"]
|
|
449
450
|
|
|
450
451
|
if pr_branch.nil? || pr_branch.empty?
|