aidevops 3.11.13 → 3.11.15

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.11.13
1
+ 3.11.15
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.11.13
8
+ # Version: 3.11.15
9
9
 
10
10
  set -euo pipefail
11
11
 
@@ -639,6 +639,58 @@ cmd_update() {
639
639
  _update_check_tools
640
640
  _update_sweep_opencode_symlinks
641
641
 
642
+ # t2898: When invoked interactively (terminal stdin AND not from the
643
+ # auto-update daemon itself, which sets AIDEVOPS_AUTO_UPDATE=1 in its
644
+ # environment), verify the daemon is healthy and warn if not. The
645
+ # advisory file gets picked up by the next session greeting so the
646
+ # user sees the warning even if they miss this output.
647
+ #
648
+ # Skip in headless / CI runs (no stdin, no TTY) to avoid spurious
649
+ # warnings in setup.sh-driven flows that already do their own check.
650
+ if [[ -t 0 ]] && [[ -z "${AIDEVOPS_AUTO_UPDATE:-}" ]] && [[ "${NON_INTERACTIVE:-false}" != "true" ]]; then
651
+ _update_check_daemon_health
652
+ fi
653
+
654
+ return 0
655
+ }
656
+
657
+ # t2898: post-update daemon health verification (interactive only).
658
+ # Side effect: writes ~/.aidevops/advisories/daemon-disabled.advisory when
659
+ # the daemon is unhealthy so the session greeting surfaces the warning.
660
+ # Cleared (file removed) when the daemon recovers.
661
+ _update_check_daemon_health() {
662
+ local helper="$HOME/.aidevops/agents/scripts/auto-update-helper.sh"
663
+ [[ -x "$helper" ]] || return 0
664
+ local advisory_dir="$HOME/.aidevops/advisories"
665
+ local advisory_file="$advisory_dir/daemon-disabled.advisory"
666
+
667
+ local hc_rc=0
668
+ "$helper" health-check --quiet >/dev/null 2>&1 || hc_rc=$?
669
+
670
+ if [[ "$hc_rc" -eq 0 ]]; then
671
+ # Healthy — clear any stale advisory.
672
+ [[ -f "$advisory_file" ]] && rm -f "$advisory_file"
673
+ return 0
674
+ fi
675
+
676
+ # Unhealthy — warn on stderr and write advisory.
677
+ mkdir -p "$advisory_dir" 2>/dev/null || return 0
678
+ local fix_cmd="aidevops auto-update enable"
679
+ [[ "$hc_rc" -eq 1 ]] && fix_cmd="aidevops auto-update check"
680
+ cat >"$advisory_file" <<EOF
681
+ auto-update daemon is not running normally on this runner. Without it, this
682
+ runner falls behind the fleet and may dispatch workers that fail because of
683
+ bugs already fixed upstream. See cross-runner-coordination.md §4.4.
684
+
685
+ Diagnose: aidevops auto-update health-check
686
+ Fix: ${fix_cmd}
687
+ EOF
688
+
689
+ if [[ "$hc_rc" -eq 1 ]]; then
690
+ print_warning "Auto-update daemon is stalled. Fix: ${fix_cmd}"
691
+ else
692
+ print_warning "Auto-update daemon is not running. Fix: ${fix_cmd}"
693
+ fi
642
694
  return 0
643
695
  }
644
696
  # Uninstall helpers (extracted for complexity reduction)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aidevops",
3
- "version": "3.11.13",
3
+ "version": "3.11.15",
4
4
  "description": "AI DevOps Framework - AI-assisted development workflows, code quality, and deployment automation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,28 @@ setup_auto_update() {
32
32
  "aidevops-auto-update"; then
33
33
  _auto_update_installed=true
34
34
  fi
35
+ # t2898: ALWAYS run idempotent re-install + health-check after the
36
+ # detection above, so every release self-heals broken installs (daemon
37
+ # unloaded by an OS update, scrubbed by a cron cleanup, etc.). The
38
+ # detection helper already handled the "freshly install if missing"
39
+ # interactive prompt; this loop ensures that even when the detection
40
+ # said "yes installed" the daemon is actually loaded right now and
41
+ # running on schedule.
42
+ if [[ "$_auto_update_installed" == "true" ]]; then
43
+ # Idempotent: no-op when daemon already loaded; re-installs only on drift.
44
+ bash "$auto_update_script" enable --idempotent >/dev/null 2>&1 || true
45
+ # Verify it's actually healthy. Surface any degradation so the
46
+ # operator sees it on every release deploy.
47
+ local _hc_rc=0
48
+ bash "$auto_update_script" health-check --quiet >/dev/null 2>&1 || _hc_rc=$?
49
+ if [[ "$_hc_rc" -eq 0 ]]; then
50
+ :
51
+ elif [[ "$_hc_rc" -eq 1 ]]; then
52
+ print_warning "Auto-update daemon installed but stalled — run: aidevops auto-update check"
53
+ elif [[ "$_hc_rc" -eq 2 ]]; then
54
+ print_warning "Auto-update daemon not loaded — run: aidevops auto-update enable"
55
+ fi
56
+ fi
35
57
  if [[ "$_auto_update_installed" == "false" ]]; then
36
58
  if [[ "$NON_INTERACTIVE" == "true" ]]; then
37
59
  # Non-interactive: enable silently
@@ -683,10 +683,16 @@ _install_pulse_launchd() {
683
683
  local _pulse_installed="$4"
684
684
  local pulse_plist="$HOME/Library/LaunchAgents/${pulse_label}.plist"
685
685
 
686
- _cleanup_old_pulse_plists "$pulse_label" "$pulse_plist"
687
-
688
- # Write the plist (always regenerated to pick up config changes)
689
- _generate_pulse_plist_content "$pulse_label" "$wrapper_script" "$opencode_bin" >"$pulse_plist"
686
+ # Capture plist content before touching the existing file.
687
+ # This avoids the "unload old, then write fails" window that leaves a 0-byte plist.
688
+ local pulse_plist_content
689
+ pulse_plist_content=$(_generate_pulse_plist_content "$pulse_label" "$wrapper_script" "$opencode_bin")
690
+
691
+ # Defensive: if generation produced empty content, refuse to touch the existing plist.
692
+ if [[ -z "$pulse_plist_content" ]]; then
693
+ print_warning "Pulse plist generation produced empty content — leaving existing plist untouched"
694
+ return 1
695
+ fi
690
696
 
691
697
  # Resolve interval for the user-facing message (matches what the plist contains).
692
698
  local _interval_sec _interval_label
@@ -697,8 +703,10 @@ _install_pulse_launchd() {
697
703
  _interval_label="${_interval_sec}s"
698
704
  fi
699
705
 
706
+ # _launchd_install_if_changed handles unload-before-replace only when content
707
+ # has changed, and writes atomically via tmp+rename (see setup.sh).
700
708
  # shell-portability: ignore next — _install_pulse_launchd is macOS-only (launchd)
701
- if launchctl load "$pulse_plist"; then
709
+ if _launchd_install_if_changed "$pulse_label" "$pulse_plist" "$pulse_plist_content"; then
702
710
  if [[ "$_pulse_installed" == "true" ]]; then
703
711
  print_info "Supervisor pulse updated (launchd config regenerated, every ${_interval_label})"
704
712
  else
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.11.13
15
+ # Version: 3.11.15
16
16
  #
17
17
  # Quick Install:
18
18
  # npm install -g aidevops && aidevops update (recommended)
@@ -241,8 +241,20 @@ _launchd_install_if_changed() {
241
241
  fi
242
242
  fi
243
243
 
244
- # Write new plist and load
245
- printf '%s\n' "$new_content" >"$plist_path"
244
+ # Atomic write: build at sibling tmp path, then rename into place.
245
+ # If printf is killed mid-write, the destination is untouched.
246
+ local tmp_plist="${plist_path}.tmp.$$"
247
+ if ! printf '%s\n' "$new_content" >"$tmp_plist"; then
248
+ rm -f "$tmp_plist"
249
+ return 1
250
+ fi
251
+ # Defensive: refuse to install an empty file (should be guaranteed by the
252
+ # caller's content check, but guard here too).
253
+ if [[ ! -s "$tmp_plist" ]]; then
254
+ rm -f "$tmp_plist"
255
+ return 1
256
+ fi
257
+ mv -f "$tmp_plist" "$plist_path"
246
258
  launchctl load "$plist_path" 2>/dev/null || return 1
247
259
  return 0
248
260
  }