aidevops 3.8.94 → 3.8.96

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.8.94
1
+ 3.8.96
package/aidevops.sh CHANGED
@@ -5,7 +5,7 @@
5
5
  # AI DevOps Framework CLI
6
6
  # Usage: aidevops <command> [options]
7
7
  #
8
- # Version: 3.8.94
8
+ # Version: 3.8.96
9
9
 
10
10
  set -euo pipefail
11
11
 
@@ -1384,10 +1384,12 @@ _help_commands() {
1384
1384
  echo " security [cmd] Full security assessment (posture + hygiene + supply chain)"
1385
1385
  echo " contributions External contributions inbox (bare: status | seed/scan/stop/restart/install/uninstall)"
1386
1386
  echo " ip-check <cmd> IP reputation checks (check/batch/report/providers)"
1387
+ echo " review-gate <cmd> Configure review_gate.rate_limit_behavior (list/set/unset)"
1387
1388
  echo " secret <cmd> Manage secrets (set/list/run/init/import/status)"
1388
1389
  echo " config <cmd> Feature toggles (list/get/set/reset/path/help)"
1389
1390
  echo " stats <cmd> LLM usage analytics (summary/models/projects/costs/trend)"
1390
1391
  echo " tabby <cmd> Manage Tabby terminal profiles (sync/status/zshrc/help)"
1392
+ echo " parent-status <N> Show decomposition state of parent-task issue #N (alias: ps)"
1391
1393
  echo " detect Find and register aidevops projects"
1392
1394
  echo " uninstall Remove aidevops from your system"
1393
1395
  echo " version Show version information"
@@ -1748,6 +1750,7 @@ main() {
1748
1750
  model-accounts-pool | map) _dispatch_helper "oauth-pool-helper.sh" "oauth-pool-helper.sh" "$@" ;;
1749
1751
  client-format) _cmd_client_format "$@" ;;
1750
1752
  opencode-sandbox | oc-sandbox) _dispatch_helper "opencode-sandbox-helper.sh" "opencode-sandbox-helper.sh" "$@" ;;
1753
+ review-gate | review_gate) _dispatch_helper "review-gate-config-helper.sh" "review-gate-config-helper.sh" "$@" ;;
1751
1754
  secret | secrets) _dispatch_helper "secret-helper.sh" "secret-helper.sh" "$@" ;;
1752
1755
  approve) _dispatch_helper "approval-helper.sh" "approval-helper.sh" "$@" ;;
1753
1756
  signing) _dispatch_helper "signing-setup.sh" "signing-setup.sh" "$@" ;;
@@ -1760,6 +1763,7 @@ main() {
1760
1763
  stats | observability) _dispatch_helper "observability-helper.sh" "observability-helper.sh" "$@" ;;
1761
1764
  tabby) _dispatch_helper "tabby-helper.sh" "tabby-helper.sh" "$@" ;;
1762
1765
  init-routines) _dispatch_helper "init-routines-helper.sh" "init-routines-helper.sh" "$@" ;;
1766
+ parent-status | ps) _dispatch_helper "parent-status-helper.sh" "parent-status-helper.sh" "$@" ;;
1763
1767
  config | configure) _dispatch_config "$@" ;;
1764
1768
  uninstall | remove) cmd_uninstall ;;
1765
1769
  version | v | -v | --version) cmd_version ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.8.94",
3
+ "version": "3.8.96",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,11 @@
9
9
  # Keep pulse workers alive long enough for opus-tier dispatches.
10
10
  PULSE_STALE_THRESHOLD_SECONDS=1800
11
11
 
12
+ # Cron expression: top of every hour. Shared by stats-wrapper,
13
+ # contribution-watch, and profile-readme schedulers — keep DRY so a
14
+ # future cadence shift only touches one place.
15
+ CRON_HOURLY="0 * * * *"
16
+
12
17
  # Resolve the modern bash binary path for use in launchd ProgramArguments.
13
18
  # Launchd bypasses the shebang when ProgramArguments specifies an explicit
14
19
  # interpreter, so we must resolve the path at plist generation time.
@@ -263,12 +268,14 @@ PULSE_STALE_THRESHOLD=${PULSE_STALE_THRESHOLD_SECONDS}"
263
268
  }
264
269
 
265
270
  # Read supervisor.pulse_interval_seconds from settings.json.
266
- # Falls back to 120 if the file is missing, the key is absent, or jq is unavailable.
271
+ # Falls back to 180 if the file is missing, the key is absent, or jq is unavailable.
267
272
  # Clamps to the validated range [30, 3600].
268
273
  # GH#18018: previously this was hardcoded as "120" in _install_supervisor_pulse.
274
+ # t2744: default raised 120 → 180 to reduce GraphQL pressure (33% fewer cycles)
275
+ # on multi-repo setups where per-cycle cost chronically exceeds 5000/hr.
269
276
  _read_pulse_interval_seconds() {
270
277
  local _settings_file="$HOME/.config/aidevops/settings.json"
271
- local _interval=120
278
+ local _interval=180
272
279
 
273
280
  if command -v jq >/dev/null 2>&1 && [[ -f "$_settings_file" ]]; then
274
281
  local _raw
@@ -492,9 +499,100 @@ _build_pulse_headless_env_xml() {
492
499
  return 0
493
500
  }
494
501
 
502
+ # Read user-owned plist env override file and emit XML key/string pairs
503
+ # for the matching label's env vars. Keys prefixed with _ are skipped
504
+ # (used as comments in the JSON template).
505
+ #
506
+ # Args: $1=plist_label (e.g. "com.aidevops.aidevops-supervisor-pulse")
507
+ # $2=override_file (absolute path; default ~/.agents/configs/plist-env-overrides.json)
508
+ # $3=indent (string to prepend each line; default "\t\t")
509
+ #
510
+ # Returns 0 on success (including empty result when label not found).
511
+ # Prints WARN to stderr and returns 0 when file is present but malformed.
512
+ # Emits nothing when file is absent.
513
+ _build_plist_env_overrides_xml() {
514
+ local _label="$1"
515
+ local _override_file="${2:-$HOME/.aidevops/agents/configs/plist-env-overrides.json}"
516
+ local _indent="${3:- }"
517
+
518
+ # Missing file is the normal case (user has not created the override file yet)
519
+ [[ -f "$_override_file" ]] || return 0
520
+
521
+ # Require jq — without it we cannot parse JSON safely
522
+ if ! command -v jq >/dev/null 2>&1; then
523
+ echo "[schedulers] WARN: jq not found; skipping plist-env-overrides.json injection" >&2
524
+ return 0
525
+ fi
526
+
527
+ # Validate JSON
528
+ if ! jq empty "$_override_file" 2>/dev/null; then
529
+ echo "[schedulers] WARN: plist-env-overrides.json is malformed; skipping injection (file: $_override_file)" >&2
530
+ return 0
531
+ fi
532
+
533
+ # Extract key=value pairs for the matching label; skip _ prefixed keys
534
+ local _pairs
535
+ _pairs=$(jq -r --arg label "$_label" '
536
+ .[$label] // {} |
537
+ to_entries[] |
538
+ select(.key | startswith("_") | not) |
539
+ "\(.key)=\(.value)"
540
+ ' "$_override_file" 2>/dev/null) || return 0
541
+
542
+ [[ -z "$_pairs" ]] && return 0
543
+
544
+ local _line _key _val _xml_key _xml_val
545
+ while IFS= read -r _line; do
546
+ [[ -z "$_line" ]] && continue
547
+ _key="${_line%%=*}"
548
+ _val="${_line#*=}"
549
+ _xml_key=$(_xml_escape "$_key")
550
+ _xml_val=$(_xml_escape "$_val")
551
+ printf '%s<key>%s</key>\n%s<string>%s</string>\n' \
552
+ "$_indent" "$_xml_key" "$_indent" "$_xml_val"
553
+ done <<<"$_pairs"
554
+
555
+ return 0
556
+ }
557
+
558
+ # Log which env var overrides were injected from plist-env-overrides.json for a label.
559
+ # Prints to stdout (setup.sh output). No-op when file absent or label not found.
560
+ # Args: $1=plist_label, $2=override_file (optional)
561
+ _log_plist_env_overrides() {
562
+ local _label="$1"
563
+ local _override_file="${2:-$HOME/.aidevops/agents/configs/plist-env-overrides.json}"
564
+
565
+ [[ -f "$_override_file" ]] || return 0
566
+ command -v jq >/dev/null 2>&1 || return 0
567
+ jq empty "$_override_file" 2>/dev/null || return 0
568
+
569
+ local _keys
570
+ _keys=$(jq -r --arg label "$_label" '
571
+ .[$label] // {} |
572
+ keys[] |
573
+ select(startswith("_") | not)
574
+ ' "$_override_file" 2>/dev/null) || return 0
575
+
576
+ [[ -z "$_keys" ]] && return 0
577
+
578
+ local _count
579
+ _count=$(echo "$_keys" | wc -l | tr -d ' ')
580
+ local _keys_inline
581
+ _keys_inline=$(echo "$_keys" | tr '\n' ' ' | sed 's/ $//')
582
+ print_info " plist-env-overrides: injected ${_count} var(s) into ${_label}: ${_keys_inline}"
583
+ return 0
584
+ }
585
+
495
586
  # Generate the full pulse launchd plist XML content.
496
587
  # Args: $1=pulse_label, $2=wrapper_script, $3=opencode_bin
497
588
  # Prints the complete plist XML to stdout.
589
+ #
590
+ # StartInterval is read from supervisor.pulse_interval_seconds in
591
+ # settings.json via _read_pulse_interval_seconds (default 180 — t2744).
592
+ # Previously this was hardcoded as 120, meaning macOS users could not
593
+ # tune the pulse cadence via settings (Linux/cron path always honoured
594
+ # the setting). The hardcoding is now removed; the macOS path matches
595
+ # the Linux path's behaviour.
498
596
  _generate_pulse_plist_content() {
499
597
  local pulse_label="$1"
500
598
  local wrapper_script="$2"
@@ -519,6 +617,17 @@ _generate_pulse_plist_content() {
519
617
  local _xml_bash_bin
520
618
  _xml_bash_bin=$(_xml_escape "$(_resolve_modern_bash)")
521
619
 
620
+ # Resolve the configured pulse interval (settings.json, with default).
621
+ # Already validated to [30, 3600] inside _read_pulse_interval_seconds.
622
+ local _pulse_interval_sec
623
+ _pulse_interval_sec=$(_read_pulse_interval_seconds)
624
+
625
+ # Inject user-owned plist env overrides (GH#20563 / t2759).
626
+ # Reads ~/.aidevops/agents/configs/plist-env-overrides.json when present.
627
+ # Missing file or label not found → emits nothing (no-op, safe default).
628
+ local _env_overrides_xml
629
+ _env_overrides_xml=$(_build_plist_env_overrides_xml "$pulse_label")
630
+
522
631
  cat <<PLIST
523
632
  <?xml version="1.0" encoding="UTF-8"?>
524
633
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -532,7 +641,7 @@ _generate_pulse_plist_content() {
532
641
  <string>${_xml_wrapper_script}</string>
533
642
  </array>
534
643
  <key>StartInterval</key>
535
- <integer>120</integer>
644
+ <integer>${_pulse_interval_sec}</integer>
536
645
  <key>StandardOutPath</key>
537
646
  <string>${_xml_home}/.aidevops/logs/pulse-wrapper.log</string>
538
647
  <key>StandardErrorPath</key>
@@ -550,7 +659,7 @@ _generate_pulse_plist_content() {
550
659
  <key>PULSE_STALE_THRESHOLD</key>
551
660
  <string>${PULSE_STALE_THRESHOLD_SECONDS}</string>
552
661
  ${_headless_xml_env}
553
- </dict>
662
+ ${_env_overrides_xml} </dict>
554
663
  <key>SoftResourceLimits</key>
555
664
  <dict>
556
665
  <key>NumberOfFiles</key>
@@ -579,13 +688,24 @@ _install_pulse_launchd() {
579
688
  # Write the plist (always regenerated to pick up config changes)
580
689
  _generate_pulse_plist_content "$pulse_label" "$wrapper_script" "$opencode_bin" >"$pulse_plist"
581
690
 
691
+ # Resolve interval for the user-facing message (matches what the plist contains).
692
+ local _interval_sec _interval_label
693
+ _interval_sec=$(_read_pulse_interval_seconds)
694
+ if (( _interval_sec % 60 == 0 )); then
695
+ _interval_label="$((_interval_sec / 60)) min"
696
+ else
697
+ _interval_label="${_interval_sec}s"
698
+ fi
699
+
582
700
  # shell-portability: ignore next — _install_pulse_launchd is macOS-only (launchd)
583
701
  if launchctl load "$pulse_plist"; then
584
702
  if [[ "$_pulse_installed" == "true" ]]; then
585
- print_info "Supervisor pulse updated (launchd config regenerated)"
703
+ print_info "Supervisor pulse updated (launchd config regenerated, every ${_interval_label})"
586
704
  else
587
- print_info "Supervisor pulse enabled (launchd, every 2 min)"
705
+ print_info "Supervisor pulse enabled (launchd, every ${_interval_label})"
588
706
  fi
707
+ # Log any user-provided env var overrides that were injected (GH#20563 / t2759)
708
+ _log_plist_env_overrides "$pulse_label"
589
709
  else
590
710
  print_warning "Failed to load supervisor pulse LaunchAgent"
591
711
  fi
@@ -940,7 +1060,10 @@ _uninstall_pulse() {
940
1060
  # Setup stats-wrapper scheduler — runs quality sweep and health issue updates
941
1061
  # separately from the pulse (t1429). Only installed when the supervisor
942
1062
  # pulse is enabled (stats are useless without it).
943
- # macOS: launchd plist (every 15 min) | Linux: systemd timer or cron (every 15 min)
1063
+ # macOS: launchd plist (hourly) | Linux: systemd timer or cron (hourly)
1064
+ # t2744: interval raised from 15 min → hourly. Stats UI is not realtime,
1065
+ # the four-times-an-hour cadence drove ~200-400 GraphQL points/hr of pure
1066
+ # overhead on multi-repo setups.
944
1067
  setup_stats_wrapper() {
945
1068
  local _pulse_lower="$1"
946
1069
  # Use effective pulse state (PULSE_ENABLED) if available; fall back to consent string.
@@ -974,7 +1097,7 @@ setup_stats_wrapper() {
974
1097
  <string>${_xml_stats_script}</string>
975
1098
  </array>
976
1099
  <key>StartInterval</key>
977
- <integer>900</integer>
1100
+ <integer>3600</integer>
978
1101
  <key>StandardOutPath</key>
979
1102
  <string>${_xml_stats_home}/.aidevops/logs/stats.log</string>
980
1103
  <key>StandardErrorPath</key>
@@ -995,7 +1118,7 @@ setup_stats_wrapper() {
995
1118
  PLIST
996
1119
  )
997
1120
  if _launchd_install_if_changed "$stats_label" "$stats_plist" "$stats_plist_content"; then
998
- print_info "Stats wrapper enabled (launchd, every 15 min)"
1121
+ print_info "Stats wrapper enabled (launchd, every hour)"
999
1122
  else
1000
1123
  print_warning "Failed to load stats wrapper LaunchAgent"
1001
1124
  fi
@@ -1003,12 +1126,12 @@ PLIST
1003
1126
  _install_scheduler_linux \
1004
1127
  "$stats_systemd" \
1005
1128
  "aidevops: stats-wrapper" \
1006
- "*/15 * * * *" \
1129
+ "$CRON_HOURLY" \
1007
1130
  "\"${stats_script}\"" \
1008
- "900" \
1131
+ "3600" \
1009
1132
  "$stats_log" \
1010
1133
  "" \
1011
- "Stats wrapper enabled (every 15 min)" \
1134
+ "Stats wrapper enabled (every hour)" \
1012
1135
  "Failed to install stats wrapper scheduler" \
1013
1136
  "true" \
1014
1137
  "false"
@@ -1503,7 +1626,7 @@ _install_cw_linux() {
1503
1626
  _install_scheduler_linux \
1504
1627
  "$cw_systemd" \
1505
1628
  "aidevops: contribution-watch" \
1506
- "0 * * * *" \
1629
+ "$CRON_HOURLY" \
1507
1630
  "\"${cw_script}\" scan" \
1508
1631
  "3600" \
1509
1632
  "${_cw_log_dir}/contribution-watch.log" \
@@ -1670,7 +1793,7 @@ _install_profile_readme_scheduler() {
1670
1793
  _install_scheduler_linux \
1671
1794
  "$pr_systemd" \
1672
1795
  "aidevops: profile-readme-update" \
1673
- "0 * * * *" \
1796
+ "$CRON_HOURLY" \
1674
1797
  "\"${pr_script}\" update" \
1675
1798
  "3600" \
1676
1799
  "$pr_log" \
@@ -388,6 +388,44 @@ setup_shell_linting_tools() {
388
388
  return 0
389
389
  }
390
390
 
391
+ setup_setsid_advisory() {
392
+ # setsid is required to detach pulse workers into their own process group
393
+ # (t2757, GH#20561). Without it, workers inherit pulse's PGID and are
394
+ # killed by any PG-scoped signal (launchd unload, restart chain).
395
+ #
396
+ # Linux: setsid ships with util-linux (present on all mainstream distros).
397
+ # macOS: available from macOS 12+ at /usr/bin/setsid. Older versions need
398
+ # util-linux via Homebrew (brew install util-linux).
399
+ if command -v setsid >/dev/null 2>&1; then
400
+ local setsid_path
401
+ setsid_path="$(command -v setsid)"
402
+ print_success "setsid found at $setsid_path (worker process-group isolation enabled)"
403
+ return 0
404
+ fi
405
+
406
+ # setsid missing — emit an advisory; it's a quality-of-life improvement,
407
+ # not a hard requirement (pulse falls back to nohup-only).
408
+ print_warning "setsid not found — pulse workers will share the pulse process group"
409
+ echo " Impact: a pulse restart or launchd unload may kill in-flight workers"
410
+ echo " (GH#20561 / t2757: worker survived 3/4 dispatches without setsid isolation)"
411
+ echo ""
412
+ if [[ "$(uname)" == "Darwin" ]]; then
413
+ if command -v brew >/dev/null 2>&1; then
414
+ echo " Install: brew install util-linux"
415
+ else
416
+ echo " Install Homebrew first, then: brew install util-linux"
417
+ echo " Or upgrade to macOS 12+ where /usr/bin/setsid ships by default"
418
+ fi
419
+ else
420
+ echo " Install: sudo apt install util-linux # Debian/Ubuntu"
421
+ echo " sudo dnf install util-linux # Fedora/RHEL"
422
+ echo " sudo pacman -S util-linux # Arch"
423
+ fi
424
+ echo ""
425
+
426
+ return 0
427
+ }
428
+
391
429
  setup_shellcheck_wrapper() {
392
430
  # Replace the real shellcheck binary with our wrapper script to prevent
393
431
  # --external-sources from causing exponential memory growth (GH#2915).
package/setup.sh CHANGED
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
12
12
  # AI Assistant Server Access Framework Setup Script
13
13
  # Helps developers set up the framework for their infrastructure
14
14
  #
15
- # Version: 3.8.94
15
+ # Version: 3.8.96
16
16
  #
17
17
  # Quick Install:
18
18
  # npm install -g aidevops && aidevops update (recommended)