aidevops 3.1.140 → 3.1.144

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.140
1
+ 3.1.144
package/aidevops.sh CHANGED
@@ -3,7 +3,7 @@
3
3
  # AI DevOps Framework CLI
4
4
  # Usage: aidevops <command> [options]
5
5
  #
6
- # Version: 3.1.140
6
+ # Version: 3.1.144
7
7
 
8
8
  set -euo pipefail
9
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.1.140",
3
+ "version": "3.1.144",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -161,27 +161,25 @@ setup_oh_my_zsh() {
161
161
  return 0
162
162
  }
163
163
 
164
- # Extract portable customizations from bash configs into a shared profile for cross-shell use
165
- setup_shell_compatibility() {
166
- print_info "Setting up cross-shell compatibility..."
167
-
168
- local shared_profile="$HOME/.shell_common"
169
- local zsh_rc="$HOME/.zshrc"
164
+ # Check that both zsh and bash are installed and collect existing bash config files.
165
+ # Prints the bash config file paths (one per line) on success.
166
+ # Returns 1 if prerequisites are not met (caller should return 0 early).
167
+ _shell_compat_check_prereqs() {
168
+ local shared_profile="$1"
169
+ local zsh_rc="$2"
170
170
 
171
- # If shared profile already exists, we've already set this up
172
171
  if [[ -f "$shared_profile" ]]; then
173
172
  print_success "Cross-shell compatibility already configured ($shared_profile)"
174
- return 0
173
+ return 1
175
174
  fi
176
175
 
177
- # Need both bash and zsh to be relevant
178
176
  if ! command -v zsh >/dev/null 2>&1; then
179
177
  print_info "zsh not installed - cross-shell setup not needed"
180
- return 0
178
+ return 1
181
179
  fi
182
180
  if ! command -v bash >/dev/null 2>&1; then
183
181
  print_info "bash not installed - cross-shell setup not needed"
184
- return 0
182
+ return 1
185
183
  fi
186
184
 
187
185
  # Collect all bash config files that exist
@@ -195,20 +193,27 @@ setup_shell_compatibility() {
195
193
 
196
194
  if [[ ${#bash_files[@]} -eq 0 ]]; then
197
195
  print_info "No bash config files found - skipping cross-shell setup"
198
- return 0
196
+ return 1
199
197
  fi
200
198
 
201
199
  if [[ ! -f "$zsh_rc" ]]; then
202
200
  print_info "No .zshrc found - skipping cross-shell setup"
203
- return 0
201
+ return 1
204
202
  fi
205
203
 
206
- # Count customizations across all bash config files
204
+ printf '%s\n' "${bash_files[@]}"
205
+ return 0
206
+ }
207
+
208
+ # Count portable customizations (exports, aliases, PATH entries) across bash config files.
209
+ # Prints "exports aliases paths" space-separated.
210
+ _shell_compat_count_customizations() {
207
211
  local total_exports=0
208
212
  local total_aliases=0
209
213
  local total_paths=0
210
214
 
211
- for src_file in "${bash_files[@]}"; do
215
+ local src_file
216
+ for src_file in "$@"; do
212
217
  local n
213
218
  # grep -c exits 1 on no match; || : prevents ERR trap noise
214
219
  # File existence already verified when building bash_files array
@@ -220,34 +225,16 @@ setup_shell_compatibility() {
220
225
  total_paths=$((total_paths + ${n:-0}))
221
226
  done
222
227
 
223
- if [[ $total_exports -eq 0 && $total_aliases -eq 0 && $total_paths -eq 0 ]]; then
224
- print_info "No bash customizations detected - skipping cross-shell setup"
225
- return 0
226
- fi
227
-
228
- print_info "Detected bash customizations across ${#bash_files[@]} file(s):"
229
- echo " Exports: $total_exports, Aliases: $total_aliases, PATH entries: $total_paths"
230
- echo ""
231
- print_info "Best practice: create a shared profile (~/.shell_common) sourced by"
232
- print_info "both bash and zsh, so your customizations work in either shell."
233
- echo ""
234
-
235
- local setup_compat="Y"
236
- if [[ "$NON_INTERACTIVE" != "true" ]]; then
237
- read -r -p "Create shared shell profile for cross-shell compatibility? [Y/n]: " setup_compat
238
- fi
239
-
240
- if [[ ! "$setup_compat" =~ ^[Yy]?$ ]]; then
241
- print_info "Skipped cross-shell compatibility setup"
242
- print_info "Set up later by creating ~/.shell_common and sourcing it from both shells"
243
- return 0
244
- fi
228
+ echo "$total_exports $total_aliases $total_paths"
229
+ return 0
230
+ }
245
231
 
246
- # Extract portable customizations from bash config into shared profile
247
- # We extract: exports, PATH modifications, aliases, eval statements, source commands
248
- # We skip: bash-specific syntax (shopt, PROMPT_COMMAND, PS1, completion, bind, etc.)
249
- # We deduplicate lines that appear in multiple files (e.g. .bash_profile sources .bashrc)
250
- print_info "Creating shared profile: $shared_profile"
232
+ # Extract portable customizations from bash config files into the shared profile.
233
+ # Returns the count of extracted lines.
234
+ _shell_compat_extract_to_shared_profile() {
235
+ local shared_profile="$1"
236
+ shift
237
+ # remaining args are bash_files
251
238
 
252
239
  {
253
240
  echo "# Shared shell profile - sourced by both bash and zsh"
@@ -262,7 +249,8 @@ setup_shell_compatibility() {
262
249
  local -a seen_lines=()
263
250
  local extracted=0
264
251
 
265
- for src_file in "${bash_files[@]}"; do
252
+ local src_file
253
+ for src_file in "$@"; do
266
254
  local src_basename
267
255
  src_basename=$(basename "$src_file")
268
256
  local added_header=false
@@ -336,14 +324,15 @@ setup_shell_compatibility() {
336
324
  done <"$src_file"
337
325
  done
338
326
 
339
- if [[ $extracted -eq 0 ]]; then
340
- rm -f "$shared_profile"
341
- print_info "No portable customizations found to extract"
342
- return 0
343
- fi
327
+ echo "$extracted"
328
+ return 0
329
+ }
344
330
 
345
- chmod 644 "$shared_profile"
346
- print_success "Extracted $extracted unique customization(s) to $shared_profile"
331
+ # Add sourcing of the shared profile to .zshrc and all bash config files.
332
+ _shell_compat_add_sourcing() {
333
+ local zsh_rc="$1"
334
+ shift
335
+ # remaining args are bash_files
347
336
 
348
337
  # Add sourcing to .zshrc if not already present (existence verified above)
349
338
  if ! grep -q 'shell_common' "$zsh_rc"; then
@@ -359,7 +348,8 @@ setup_shell_compatibility() {
359
348
 
360
349
  # Add sourcing to bash config files if not already present
361
350
  # File existence already verified when building bash_files array
362
- for src_file in "${bash_files[@]}"; do
351
+ local src_file
352
+ for src_file in "$@"; do
363
353
  if ! grep -q 'shell_common' "$src_file"; then
364
354
  {
365
355
  echo ""
@@ -372,6 +362,73 @@ setup_shell_compatibility() {
372
362
  fi
373
363
  done
374
364
 
365
+ return 0
366
+ }
367
+
368
+ # Extract portable customizations from bash configs into a shared profile for cross-shell use
369
+ setup_shell_compatibility() {
370
+ print_info "Setting up cross-shell compatibility..."
371
+
372
+ local shared_profile="$HOME/.shell_common"
373
+ local zsh_rc="$HOME/.zshrc"
374
+
375
+ # Check prerequisites; collect bash config files
376
+ local -a bash_files=()
377
+ local prereq_out
378
+ prereq_out=$(_shell_compat_check_prereqs "$shared_profile" "$zsh_rc") || return 0
379
+ while IFS= read -r line; do
380
+ [[ -n "$line" ]] && bash_files+=("$line")
381
+ done <<<"$prereq_out"
382
+
383
+ # Count customizations across all bash config files
384
+ local counts
385
+ counts=$(_shell_compat_count_customizations "${bash_files[@]}")
386
+ local total_exports total_aliases total_paths
387
+ read -r total_exports total_aliases total_paths <<<"$counts"
388
+
389
+ if [[ $total_exports -eq 0 && $total_aliases -eq 0 && $total_paths -eq 0 ]]; then
390
+ print_info "No bash customizations detected - skipping cross-shell setup"
391
+ return 0
392
+ fi
393
+
394
+ print_info "Detected bash customizations across ${#bash_files[@]} file(s):"
395
+ echo " Exports: $total_exports, Aliases: $total_aliases, PATH entries: $total_paths"
396
+ echo ""
397
+ print_info "Best practice: create a shared profile (~/.shell_common) sourced by"
398
+ print_info "both bash and zsh, so your customizations work in either shell."
399
+ echo ""
400
+
401
+ local setup_compat="Y"
402
+ if [[ "$NON_INTERACTIVE" != "true" ]]; then
403
+ read -r -p "Create shared shell profile for cross-shell compatibility? [Y/n]: " setup_compat
404
+ fi
405
+
406
+ if [[ ! "$setup_compat" =~ ^[Yy]?$ ]]; then
407
+ print_info "Skipped cross-shell compatibility setup"
408
+ print_info "Set up later by creating ~/.shell_common and sourcing it from both shells"
409
+ return 0
410
+ fi
411
+
412
+ # Extract portable customizations from bash config into shared profile
413
+ # We extract: exports, PATH modifications, aliases, eval statements, source commands
414
+ # We skip: bash-specific syntax (shopt, PROMPT_COMMAND, PS1, completion, bind, etc.)
415
+ # We deduplicate lines that appear in multiple files (e.g. .bash_profile sources .bashrc)
416
+ print_info "Creating shared profile: $shared_profile"
417
+
418
+ local extracted
419
+ extracted=$(_shell_compat_extract_to_shared_profile "$shared_profile" "${bash_files[@]}")
420
+
421
+ if [[ $extracted -eq 0 ]]; then
422
+ rm -f "$shared_profile"
423
+ print_info "No portable customizations found to extract"
424
+ return 0
425
+ fi
426
+
427
+ chmod 644 "$shared_profile"
428
+ print_success "Extracted $extracted unique customization(s) to $shared_profile"
429
+
430
+ _shell_compat_add_sourcing "$zsh_rc" "${bash_files[@]}"
431
+
375
432
  echo ""
376
433
  print_success "Cross-shell compatibility configured"
377
434
  print_info "Your customizations are now in: $shared_profile"
@@ -499,53 +556,42 @@ add_local_bin_to_path() {
499
556
  # If it appears after /opt/homebrew/bin, the real shellcheck is found first
500
557
  # and the wrapper is bypassed entirely. The launchctl setenv always prepends,
501
558
  # and the case-guard in shell rc files ensures it stays first.
502
- setup_shellcheck_wrapper() {
503
- local wrapper_path="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"
504
559
 
505
- # Verify the wrapper exists and is executable
560
+ # Verify the shellcheck wrapper exists and is executable.
561
+ # Returns 1 if setup should be aborted (caller returns 0).
562
+ _shellcheck_wrapper_verify() {
563
+ local wrapper_path="$1"
564
+
506
565
  if [[ ! -x "$wrapper_path" ]]; then
507
566
  if [[ -f "$wrapper_path" ]]; then
508
567
  chmod +x "$wrapper_path"
509
568
  else
510
569
  print_warning "ShellCheck wrapper not found at $wrapper_path (will be available after deploy)"
511
- return 0
570
+ return 1
512
571
  fi
513
572
  fi
514
573
 
515
- # Verify the wrapper actually works (can find real shellcheck)
516
574
  if ! "$wrapper_path" --version >/dev/null 2>&1; then
517
575
  print_warning "ShellCheck wrapper cannot find real shellcheck binary — skipping"
518
- return 0
576
+ return 1
519
577
  fi
520
578
 
521
- local env_line
522
- # shellcheck disable=SC2016 # env_line is written to rc files; must expand at shell startup
523
- env_line='export SHELLCHECK_PATH="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"'
524
- # shellcheck disable=SC2016 # path_line is written to rc files; must expand at shell startup
525
- # Sanitize-and-prepend: strip any existing occurrence of the shim dir from PATH
526
- # (it may be at the END from a previous setup run), then prepend it. This ensures
527
- # the shim is always first, even on machines upgrading from the old append form.
528
- # The ${PATH:+:$PATH} guard handles the empty-PATH edge case without a trailing colon.
529
- local path_line='_aidevops_shim="$HOME/.aidevops/bin"; PATH="$(printf '\''%s'\'' "$PATH" | tr '\'':'\'' '\''\n'\'' | grep -Fxv -- "$_aidevops_shim" | paste -sd: -)"; export PATH="$_aidevops_shim${PATH:+:$PATH}"; unset _aidevops_shim'
530
- # Fish shell uses different syntax (set -gx instead of export)
531
- # shellcheck disable=SC2016 # fish lines are written to config.fish; must expand at shell startup
532
- local env_line_fish='set -gx SHELLCHECK_PATH "$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"'
533
- # shellcheck disable=SC2016 # fish path line: strip existing, then prepend
534
- local path_line_fish='set -l _aidevops_shim "$HOME/.aidevops/bin"; set -l _aidevops_rest (string match -v -- "$_aidevops_shim" $PATH); set -gx PATH $_aidevops_shim $_aidevops_rest'
535
- local added_to=""
536
- local already_in=""
579
+ return 0
580
+ }
537
581
 
538
- # Layer 0: PATH shim (GH#2993)
539
- # Create ~/.aidevops/bin/shellcheck as a symlink to the wrapper.
540
- # This is the primary fix: bash-language-server resolves `shellcheck` via
541
- # PATH, so the symlink must appear on PATH before /opt/homebrew/bin.
582
+ # Layer 0: Create the PATH shim (GH#2993).
583
+ # Places a symlink to the wrapper in ~/.aidevops/bin so bash-language-server
584
+ # finds the wrapper via PATH lookup before the real shellcheck binary.
585
+ _shellcheck_wrapper_setup_shim() {
586
+ local wrapper_path="$1"
542
587
  local shim_dir="$HOME/.aidevops/bin"
543
588
  local shim_path="$shim_dir/shellcheck"
589
+
544
590
  mkdir -p "$shim_dir"
545
591
 
546
- # Create or update the symlink
547
592
  local wrapper_realpath
548
593
  wrapper_realpath="$(realpath "$wrapper_path" 2>/dev/null || readlink -f "$wrapper_path" 2>/dev/null || echo "$wrapper_path")"
594
+
549
595
  if [[ -L "$shim_path" ]]; then
550
596
  local current_target
551
597
  current_target="$(realpath "$shim_path" 2>/dev/null || readlink -f "$shim_path" 2>/dev/null || echo "")"
@@ -563,57 +609,64 @@ setup_shellcheck_wrapper() {
563
609
  print_success "Created shellcheck shim: $shim_path → $wrapper_path"
564
610
  fi
565
611
 
566
- # Layer 1: launchctl setenv (macOS) — affects all GUI-launched processes
567
- # Set both SHELLCHECK_PATH (for tools that honour it) and PATH (for tools
568
- # that resolve shellcheck via PATH lookup, like bash-language-server).
612
+ return 0
613
+ }
614
+
615
+ # Layer 1: Set SHELLCHECK_PATH and prepend shim dir via launchctl (macOS only).
616
+ # Affects all GUI-launched processes for the current boot session.
617
+ _shellcheck_wrapper_setup_launchctl() {
618
+ local wrapper_path="$1"
619
+ local shim_dir="$2"
620
+
569
621
  # Note: 2>/dev/null on launchctl is intentional — launchctl may not be
570
622
  # available in non-GUI contexts (SSH, containers). Unlike grep where we
571
623
  # want errors visible, launchctl failure is a non-fatal fallback.
572
- #
573
- # CRITICAL: Always prepend shim_dir even if it's already in PATH — it may
574
- # be at the END (e.g., appended by a previous setup run), which means the
575
- # real shellcheck at /opt/homebrew/bin is found first. We strip any existing
576
- # occurrence and prepend to guarantee first position.
577
- if [[ "$PLATFORM_MACOS" == "true" ]]; then
578
- if launchctl setenv SHELLCHECK_PATH "$wrapper_path" 2>/dev/null; then
579
- print_info "Set SHELLCHECK_PATH via launchctl (GUI processes)"
580
- fi
581
- # Build a clean PATH with shim_dir at the front, removing any existing
582
- # occurrence to prevent duplicates while ensuring first position.
583
- # Handle the empty-PATH edge case to avoid a trailing colon (which
584
- # resolves to "." and is a PATH injection vector).
585
- local clean_path
586
- clean_path=$(printf '%s' "$PATH" | tr ':' '\n' | grep -Fxv "$shim_dir" | tr '\n' ':' | sed 's/:$//')
587
- local new_path
588
- if [[ -n "$clean_path" ]]; then
589
- new_path="${shim_dir}:${clean_path}"
590
- else
591
- new_path="${shim_dir}"
592
- fi
593
- if launchctl setenv PATH "$new_path" 2>/dev/null; then
594
- print_info "Prepended $shim_dir to PATH via launchctl (GUI processes)"
595
- fi
624
+ if launchctl setenv SHELLCHECK_PATH "$wrapper_path" 2>/dev/null; then
625
+ print_info "Set SHELLCHECK_PATH via launchctl (GUI processes)"
596
626
  fi
597
627
 
598
- # Layer 2: .zshenv affects ALL zsh processes (interactive AND non-interactive)
599
- # This is critical because opencode spawns bash-language-server as a
600
- # non-interactive child process. .zshrc is NOT sourced for non-interactive
601
- # shells, so SHELLCHECK_PATH set only in .zshrc is invisible to the LSP.
602
- # GH#2993: Also prepend ~/.aidevops/bin to PATH here so the shim is found.
628
+ # Build a clean PATH with shim_dir at the front, removing any existing
629
+ # occurrence to prevent duplicates while ensuring first position.
630
+ # Handle the empty-PATH edge case to avoid a trailing colon (which
631
+ # resolves to "." and is a PATH injection vector).
632
+ local clean_path
633
+ clean_path=$(printf '%s' "$PATH" | tr ':' '\n' | grep -Fxv "$shim_dir" | tr '\n' ':' | sed 's/:$//')
634
+ local new_path
635
+ if [[ -n "$clean_path" ]]; then
636
+ new_path="${shim_dir}:${clean_path}"
637
+ else
638
+ new_path="${shim_dir}"
639
+ fi
640
+ if launchctl setenv PATH "$new_path" 2>/dev/null; then
641
+ print_info "Prepended $shim_dir to PATH via launchctl (GUI processes)"
642
+ fi
643
+
644
+ return 0
645
+ }
646
+
647
+ # Layer 2: Configure SHELLCHECK_PATH and PATH shim in .zshenv.
648
+ # Affects ALL zsh processes including non-interactive (e.g. bash-language-server).
649
+ _shellcheck_wrapper_setup_zshenv() {
650
+ local env_line="$1"
651
+ local path_line="$2"
652
+ local shim_dir="$3"
603
653
  local zshenv="$HOME/.zshenv"
654
+ local added_to_out=""
655
+ local already_in_out=""
656
+
604
657
  if [[ -f "$zshenv" ]] || command -v zsh >/dev/null 2>&1; then
605
658
  touch "$zshenv"
606
659
 
607
660
  # SHELLCHECK_PATH env var (for tools that honour it)
608
661
  if grep -q 'SHELLCHECK_PATH' "$zshenv"; then
609
- already_in="${already_in:+$already_in, }$zshenv"
662
+ already_in_out="$zshenv"
610
663
  else
611
664
  {
612
665
  echo ""
613
666
  echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)"
614
667
  echo "$env_line"
615
668
  } >>"$zshenv"
616
- added_to="${added_to:+$added_to, }$zshenv"
669
+ added_to_out="$zshenv"
617
670
  fi
618
671
 
619
672
  # PATH prepend for ~/.aidevops/bin (GH#2993: shim must be on PATH)
@@ -639,7 +692,20 @@ setup_shellcheck_wrapper() {
639
692
  fi
640
693
  fi
641
694
 
642
- # Layer 3: Shell rc files — affects interactive terminal sessions
695
+ # Return values via stdout (space-separated)
696
+ echo "$added_to_out $already_in_out"
697
+ return 0
698
+ }
699
+
700
+ # Layer 3: Configure SHELLCHECK_PATH and PATH shim in interactive shell rc files.
701
+ _shellcheck_wrapper_setup_rc_files() {
702
+ local env_line="$1"
703
+ local path_line="$2"
704
+ local env_line_fish="$3"
705
+ local path_line_fish="$4"
706
+ local added_to_out=""
707
+ local already_in_out=""
708
+
643
709
  local rc_file
644
710
  while IFS= read -r rc_file; do
645
711
  [[ -z "$rc_file" ]] && continue
@@ -665,14 +731,14 @@ setup_shellcheck_wrapper() {
665
731
 
666
732
  # SHELLCHECK_PATH env var
667
733
  if grep -q 'SHELLCHECK_PATH' "$rc_file"; then
668
- already_in="${already_in:+$already_in, }$rc_file"
734
+ already_in_out="${already_in_out:+$already_in_out, }$rc_file"
669
735
  else
670
736
  {
671
737
  echo ""
672
738
  echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)"
673
739
  echo "$rc_env_line"
674
740
  } >>"$rc_file"
675
- added_to="${added_to:+$added_to, }$rc_file"
741
+ added_to_out="${added_to_out:+$added_to_out, }$rc_file"
676
742
  fi
677
743
 
678
744
  # PATH prepend for ~/.aidevops/bin (GH#2993)
@@ -699,6 +765,72 @@ setup_shellcheck_wrapper() {
699
765
  fi
700
766
  done < <(get_all_shell_rcs)
701
767
 
768
+ echo "$added_to_out|$already_in_out"
769
+ return 0
770
+ }
771
+
772
+ setup_shellcheck_wrapper() {
773
+ local wrapper_path="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"
774
+
775
+ _shellcheck_wrapper_verify "$wrapper_path" || return 0
776
+
777
+ local env_line
778
+ # shellcheck disable=SC2016 # env_line is written to rc files; must expand at shell startup
779
+ env_line='export SHELLCHECK_PATH="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"'
780
+ # shellcheck disable=SC2016 # path_line is written to rc files; must expand at shell startup
781
+ # Sanitize-and-prepend: strip any existing occurrence of the shim dir from PATH
782
+ # (it may be at the END from a previous setup run), then prepend it. This ensures
783
+ # the shim is always first, even on machines upgrading from the old append form.
784
+ # The ${PATH:+:$PATH} guard handles the empty-PATH edge case without a trailing colon.
785
+ local path_line='_aidevops_shim="$HOME/.aidevops/bin"; PATH="$(printf '\''%s'\'' "$PATH" | tr '\'':'\'' '\''\n'\'' | grep -Fxv -- "$_aidevops_shim" | paste -sd: -)"; export PATH="$_aidevops_shim${PATH:+:$PATH}"; unset _aidevops_shim'
786
+ # Fish shell uses different syntax (set -gx instead of export)
787
+ # shellcheck disable=SC2016 # fish lines are written to config.fish; must expand at shell startup
788
+ local env_line_fish='set -gx SHELLCHECK_PATH "$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"'
789
+ # shellcheck disable=SC2016 # fish path line: strip existing, then prepend
790
+ local path_line_fish='set -l _aidevops_shim "$HOME/.aidevops/bin"; set -l _aidevops_rest (string match -v -- "$_aidevops_shim" $PATH); set -gx PATH $_aidevops_shim $_aidevops_rest'
791
+
792
+ local shim_dir="$HOME/.aidevops/bin"
793
+
794
+ # Layer 0: PATH shim (GH#2993)
795
+ _shellcheck_wrapper_setup_shim "$wrapper_path"
796
+
797
+ # Layer 1: launchctl setenv (macOS) — affects all GUI-launched processes
798
+ # Set both SHELLCHECK_PATH (for tools that honour it) and PATH (for tools
799
+ # that resolve shellcheck via PATH lookup, like bash-language-server).
800
+ # CRITICAL: Always prepend shim_dir even if it's already in PATH — it may
801
+ # be at the END (e.g., appended by a previous setup run), which means the
802
+ # real shellcheck at /opt/homebrew/bin is found first. We strip any existing
803
+ # occurrence and prepend to guarantee first position.
804
+ if [[ "$PLATFORM_MACOS" == "true" ]]; then
805
+ _shellcheck_wrapper_setup_launchctl "$wrapper_path" "$shim_dir"
806
+ fi
807
+
808
+ # Layer 2: .zshenv — affects ALL zsh processes (interactive AND non-interactive)
809
+ # This is critical because opencode spawns bash-language-server as a
810
+ # non-interactive child process. .zshrc is NOT sourced for non-interactive
811
+ # shells, so SHELLCHECK_PATH set only in .zshrc is invisible to the LSP.
812
+ # GH#2993: Also prepend ~/.aidevops/bin to PATH here so the shim is found.
813
+ local zshenv_result
814
+ zshenv_result=$(_shellcheck_wrapper_setup_zshenv "$env_line" "$path_line" "$shim_dir")
815
+ local zshenv_added zshenv_already
816
+ zshenv_added="${zshenv_result%% *}"
817
+ zshenv_already="${zshenv_result##* }"
818
+
819
+ # Layer 3: Shell rc files — affects interactive terminal sessions
820
+ local rc_result
821
+ rc_result=$(_shellcheck_wrapper_setup_rc_files "$env_line" "$path_line" "$env_line_fish" "$path_line_fish")
822
+ local rc_added rc_already
823
+ rc_added="${rc_result%%|*}"
824
+ rc_already="${rc_result##*|}"
825
+
826
+ # Merge results from layers 2 and 3
827
+ local added_to=""
828
+ local already_in=""
829
+ [[ -n "$zshenv_added" && "$zshenv_added" != " " ]] && added_to="${zshenv_added}"
830
+ [[ -n "$rc_added" ]] && added_to="${added_to:+$added_to, }${rc_added}"
831
+ [[ -n "$zshenv_already" && "$zshenv_already" != " " ]] && already_in="${zshenv_already}"
832
+ [[ -n "$rc_already" ]] && already_in="${already_in:+$already_in, }${rc_already}"
833
+
702
834
  if [[ -n "$added_to" ]]; then
703
835
  print_success "Configured SHELLCHECK_PATH wrapper in: $added_to"
704
836
  fi
@@ -719,6 +851,26 @@ setup_shellcheck_wrapper() {
719
851
  return 0
720
852
  }
721
853
 
854
+ # Check whether server access aliases are already configured in any rc file.
855
+ # Returns 0 (true) if already configured, 1 (false) if not.
856
+ _aliases_check_configured() {
857
+ local rc_file
858
+ while IFS= read -r rc_file; do
859
+ [[ -z "$rc_file" ]] && continue
860
+ if [[ -f "$rc_file" ]] && grep -q "# AI Assistant Server Access" "$rc_file"; then
861
+ return 0
862
+ fi
863
+ done < <(get_all_shell_rcs)
864
+
865
+ # Also check fish config (not included in get_all_shell_rcs on macOS)
866
+ local fish_config="$HOME/.config/fish/config.fish"
867
+ if [[ -f "$fish_config" ]] && grep -q "# AI Assistant Server Access" "$fish_config"; then
868
+ return 0
869
+ fi
870
+
871
+ return 1
872
+ }
873
+
722
874
  # Add server access aliases to shell rc files (bash/zsh/fish)
723
875
  setup_aliases() {
724
876
  print_info "Setting up shell aliases..."
@@ -758,25 +910,7 @@ alias aws-helper './.agents/scripts/aws-helper.sh'
758
910
  ALIASES
759
911
  )
760
912
 
761
- # Check if aliases already exist in any rc file (including fish config)
762
- local any_configured=false
763
- local rc_file
764
- while IFS= read -r rc_file; do
765
- [[ -z "$rc_file" ]] && continue
766
- if [[ -f "$rc_file" ]] && grep -q "# AI Assistant Server Access" "$rc_file"; then
767
- any_configured=true
768
- break
769
- fi
770
- done < <(get_all_shell_rcs)
771
- # Also check fish config (not included in get_all_shell_rcs on macOS)
772
- if [[ "$any_configured" == "false" ]]; then
773
- local fish_config="$HOME/.config/fish/config.fish"
774
- if [[ -f "$fish_config" ]] && grep -q "# AI Assistant Server Access" "$fish_config"; then
775
- any_configured=true
776
- fi
777
- fi
778
-
779
- if [[ "$any_configured" == "true" ]]; then
913
+ if _aliases_check_configured; then
780
914
  print_info "Server Access aliases already configured - Skipping"
781
915
  return 0
782
916
  fi
@@ -795,6 +929,7 @@ ALIASES
795
929
  added_to="$fish_rc"
796
930
  else
797
931
  # Add to all bash/zsh rc files
932
+ local rc_file
798
933
  while IFS= read -r rc_file; do
799
934
  [[ -z "$rc_file" ]] && continue
800
935