aidevops 3.11.17 → 3.13.0
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 +1 -1
- package/aidevops.sh +130 -1
- package/package.json +1 -1
- package/setup-modules/schedulers.sh +414 -3
- package/setup-modules/tool-install.sh +45 -15
- package/setup.sh +95 -3
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.13.0
|
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
|
+
# Version: 3.13.0
|
|
9
9
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
@@ -480,6 +480,46 @@ _update_check_homebrew() {
|
|
|
480
480
|
return 0
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
+
# t2926 / GH#21102: Re-check setsid on every 'aidevops update' run.
|
|
484
|
+
# setsid (from util-linux) is required to detach pulse workers into their own
|
|
485
|
+
# process group — without it, every pulse restart sends SIGHUP to its PGID,
|
|
486
|
+
# killing in-flight workers. This check runs even when setup.sh is skipped
|
|
487
|
+
# (already up-to-date path), so Homebrew drift doesn't silently break workers.
|
|
488
|
+
_update_check_setsid() {
|
|
489
|
+
command -v setsid >/dev/null 2>&1 && return 0
|
|
490
|
+
|
|
491
|
+
# setsid is missing. On macOS with Homebrew, auto-install util-linux.
|
|
492
|
+
# Use a boolean flag to avoid repeating the OS literal string.
|
|
493
|
+
local _on_mac=false
|
|
494
|
+
[[ "$(uname -s)" == Darwin* ]] && _on_mac=true
|
|
495
|
+
if $_on_mac && command -v brew >/dev/null 2>&1; then
|
|
496
|
+
print_info "setsid not found — installing util-linux for worker PGID isolation (GH#21102)"
|
|
497
|
+
if brew install util-linux 2>&1 | tail -3; then
|
|
498
|
+
local brew_prefix=""
|
|
499
|
+
brew_prefix="$(brew --prefix 2>/dev/null || true)"
|
|
500
|
+
local keg_setsid="${brew_prefix}/opt/util-linux/bin/setsid"
|
|
501
|
+
local link_target="${brew_prefix}/bin/setsid"
|
|
502
|
+
if [[ -x "$keg_setsid" && ! -e "$link_target" ]]; then
|
|
503
|
+
ln -s "$keg_setsid" "$link_target" && \
|
|
504
|
+
print_success "Symlinked setsid: $keg_setsid → $link_target"
|
|
505
|
+
fi
|
|
506
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
507
|
+
print_success "setsid installed at $(command -v setsid) (worker PGID isolation enabled)"
|
|
508
|
+
else
|
|
509
|
+
print_error "util-linux installed but setsid still not in PATH — check brew --prefix"
|
|
510
|
+
fi
|
|
511
|
+
else
|
|
512
|
+
print_error "brew install util-linux failed — workers will share pulse PGID until resolved"
|
|
513
|
+
fi
|
|
514
|
+
elif $_on_mac; then
|
|
515
|
+
print_error "setsid not found — worker isolation broken; install Homebrew then run: brew install util-linux"
|
|
516
|
+
else
|
|
517
|
+
print_error "setsid not found — worker isolation broken; install util-linux via your distro package manager"
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
return 0
|
|
521
|
+
}
|
|
522
|
+
|
|
483
523
|
# Verify supply chain signature after pulling framework updates.
|
|
484
524
|
# Checks that the HEAD commit is signed by the trusted maintainer key.
|
|
485
525
|
# Non-blocking: warns on failure, does not abort the update.
|
|
@@ -638,6 +678,8 @@ cmd_update() {
|
|
|
638
678
|
_update_check_planning
|
|
639
679
|
_update_check_tools
|
|
640
680
|
_update_sweep_opencode_symlinks
|
|
681
|
+
# t2926: Re-check setsid on every update (runs even when setup.sh is skipped).
|
|
682
|
+
_update_check_setsid
|
|
641
683
|
|
|
642
684
|
# t2898: When invoked interactively (terminal stdin AND not from the
|
|
643
685
|
# auto-update daemon itself, which sets AIDEVOPS_AUTO_UPDATE=1 in its
|
|
@@ -1456,10 +1498,13 @@ _help_commands() {
|
|
|
1456
1498
|
echo " approve <cmd> Cryptographic issue/PR approval (setup/issue/pr/verify/status)"
|
|
1457
1499
|
echo " security [cmd] Full security assessment (posture + hygiene + supply chain)"
|
|
1458
1500
|
echo " contributions External contributions inbox (bare: status | seed/scan/stop/restart/install/uninstall)"
|
|
1501
|
+
echo " inbox [cmd] Capture transit zone (bare: status | provision/add/find/digest/help)"
|
|
1502
|
+
echo " email [cmd] Email mailbox management (mailbox add/list/test/remove)"
|
|
1459
1503
|
echo " ip-check <cmd> IP reputation checks (check/batch/report/providers)"
|
|
1460
1504
|
echo " review-gate <cmd> Configure review_gate.rate_limit_behavior (list/set/unset)"
|
|
1461
1505
|
echo " secret <cmd> Manage secrets (set/list/run/init/import/status)"
|
|
1462
1506
|
echo " config <cmd> Feature toggles (list/get/set/reset/path/help)"
|
|
1507
|
+
echo " knowledge <cmd> Knowledge plane management (init/status/provision)"
|
|
1463
1508
|
echo " stats <cmd> LLM usage analytics (summary/models/projects/costs/trend)"
|
|
1464
1509
|
echo " tabby <cmd> Manage Tabby terminal profiles (sync/status/zshrc/help)"
|
|
1465
1510
|
echo " parent-status <N> Show decomposition state of parent-task issue #N (alias: ps)"
|
|
@@ -1539,6 +1584,13 @@ _help_detailed_sections() {
|
|
|
1539
1584
|
echo " aidevops config reset [key] # Reset toggle(s) to defaults"
|
|
1540
1585
|
echo " aidevops config path # Show config file path"
|
|
1541
1586
|
echo ""
|
|
1587
|
+
echo "Knowledge Plane:"
|
|
1588
|
+
echo " aidevops knowledge init repo # Provision _knowledge/ in current repo"
|
|
1589
|
+
echo " aidevops knowledge init personal # Provision at ~/.aidevops/.agent-workspace/knowledge/"
|
|
1590
|
+
echo " aidevops knowledge init off # Disable knowledge plane"
|
|
1591
|
+
echo " aidevops knowledge status # Show provisioning state"
|
|
1592
|
+
echo " aidevops knowledge provision [path] # Re-provision (idempotent)"
|
|
1593
|
+
echo ""
|
|
1542
1594
|
echo "LLM Stats:"
|
|
1543
1595
|
echo " aidevops stats # Show usage summary (last 30 days)"
|
|
1544
1596
|
echo " aidevops stats summary # Overall usage summary"
|
|
@@ -1745,6 +1797,71 @@ _cmd_security() {
|
|
|
1745
1797
|
return 0
|
|
1746
1798
|
}
|
|
1747
1799
|
|
|
1800
|
+
# Route 'aidevops email [subcommand]' to email helpers
|
|
1801
|
+
_cmd_email() {
|
|
1802
|
+
local sub="${1:-help}"
|
|
1803
|
+
local _EPH="email-poll-helper.sh"
|
|
1804
|
+
shift || true
|
|
1805
|
+
case "$sub" in
|
|
1806
|
+
mailbox)
|
|
1807
|
+
local action="${1:-list}"
|
|
1808
|
+
shift || true
|
|
1809
|
+
local _EMR_HELPER="email-mailbox-register-helper.sh"
|
|
1810
|
+
case "$action" in
|
|
1811
|
+
add) _dispatch_helper "$_EMR_HELPER" "$_EMR_HELPER" add "$@" ;;
|
|
1812
|
+
list) _dispatch_helper "$_EPH" "$_EPH" list "$@" ;;
|
|
1813
|
+
test) _dispatch_helper "$_EPH" "$_EPH" test "$@" ;;
|
|
1814
|
+
remove) _dispatch_helper "$_EMR_HELPER" "$_EMR_HELPER" remove "$@" ;;
|
|
1815
|
+
*)
|
|
1816
|
+
echo "Usage: aidevops email mailbox <add|list|test|remove>"
|
|
1817
|
+
echo ""
|
|
1818
|
+
echo "Mailbox subcommands:"
|
|
1819
|
+
echo " add Interactive: prompt for provider, user, gopass path; test connection"
|
|
1820
|
+
echo " list Table of mailboxes with last-polled-at and last-error"
|
|
1821
|
+
echo " test <id> Dry-run fetch (1 message); does not commit state"
|
|
1822
|
+
echo " remove <id> Un-register a mailbox"
|
|
1823
|
+
;;
|
|
1824
|
+
esac
|
|
1825
|
+
;;
|
|
1826
|
+
poll)
|
|
1827
|
+
# Direct poll commands forwarded to email-poll-helper.sh
|
|
1828
|
+
local poll_action="${1:-tick}"
|
|
1829
|
+
shift || true
|
|
1830
|
+
_dispatch_helper "$_EPH" "$_EPH" "$poll_action" "$@" ;;
|
|
1831
|
+
thread)
|
|
1832
|
+
# Thread lookup: email thread <message-id> [knowledge-root]
|
|
1833
|
+
local _ETH="email-thread-helper.sh"
|
|
1834
|
+
_dispatch_helper "$_ETH" "$_ETH" thread "$@" ;;
|
|
1835
|
+
build)
|
|
1836
|
+
# Thread rebuild: email build [knowledge-root] [--force]
|
|
1837
|
+
local _ETH2="email-thread-helper.sh"
|
|
1838
|
+
_dispatch_helper "$_ETH2" "$_ETH2" build "$@" ;;
|
|
1839
|
+
filter)
|
|
1840
|
+
# Filter rules: email filter tick|add|test|list [knowledge-root]
|
|
1841
|
+
local _EFH="email-filter-helper.sh"
|
|
1842
|
+
[[ $# -eq 0 ]] && set -- list
|
|
1843
|
+
_dispatch_helper "$_EFH" "$_EFH" "$@" ;;
|
|
1844
|
+
*)
|
|
1845
|
+
echo "Usage: aidevops email <mailbox|poll|thread|build|filter> [subcommand]"
|
|
1846
|
+
echo ""
|
|
1847
|
+
echo "Email subcommands:"
|
|
1848
|
+
echo " mailbox add Register a new IMAP mailbox (interactive)"
|
|
1849
|
+
echo " mailbox list Show all mailboxes + polling status"
|
|
1850
|
+
echo " mailbox test <id> Dry-run connection test"
|
|
1851
|
+
echo " mailbox remove <id> Un-register a mailbox"
|
|
1852
|
+
echo " poll tick Poll all mailboxes now (same as routine r044)"
|
|
1853
|
+
echo " poll backfill <id> Backfill a mailbox from a given date"
|
|
1854
|
+
echo " thread <message-id> Look up thread by message-id"
|
|
1855
|
+
echo " build [--force] Rebuild thread index from email sources"
|
|
1856
|
+
echo " filter list List filter rules"
|
|
1857
|
+
echo " filter add Add a new filter rule (interactive)"
|
|
1858
|
+
echo " filter test <rule> Dry-run rule against last 50 sources"
|
|
1859
|
+
echo " filter tick Run filter pass (routine r045)"
|
|
1860
|
+
;;
|
|
1861
|
+
esac
|
|
1862
|
+
return 0
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1748
1865
|
# Route 'aidevops client-format [subcommand]' to appropriate helpers
|
|
1749
1866
|
_cmd_client_format() {
|
|
1750
1867
|
case "${1:-status}" in
|
|
@@ -1835,10 +1952,22 @@ main() {
|
|
|
1835
1952
|
[[ $# -eq 0 ]] && set -- status
|
|
1836
1953
|
_dispatch_helper "contribution-watch-helper.sh" "contribution-watch-helper.sh" "$@"
|
|
1837
1954
|
;;
|
|
1955
|
+
inbox)
|
|
1956
|
+
# Bare `aidevops inbox` defaults to status (most common use).
|
|
1957
|
+
[[ $# -eq 0 ]] && set -- status
|
|
1958
|
+
_dispatch_helper "inbox-helper.sh" "inbox-helper.sh" "$@"
|
|
1959
|
+
;;
|
|
1960
|
+
case | cases)
|
|
1961
|
+
# Bare `aidevops case` defaults to list (most common use).
|
|
1962
|
+
[[ $# -eq 0 ]] && set -- list
|
|
1963
|
+
_dispatch_helper "case-helper.sh" "case-helper.sh" "$@"
|
|
1964
|
+
;;
|
|
1965
|
+
email) _cmd_email "$@" ;;
|
|
1838
1966
|
stats | observability) _dispatch_helper "observability-helper.sh" "observability-helper.sh" "$@" ;;
|
|
1839
1967
|
tabby) _dispatch_helper "tabby-helper.sh" "tabby-helper.sh" "$@" ;;
|
|
1840
1968
|
init-routines) _dispatch_helper "init-routines-helper.sh" "init-routines-helper.sh" "$@" ;;
|
|
1841
1969
|
parent-status | ps) _dispatch_helper "parent-status-helper.sh" "parent-status-helper.sh" "$@" ;;
|
|
1970
|
+
knowledge) _dispatch_helper "knowledge-helper.sh" "knowledge-helper.sh" "$@" ;;
|
|
1842
1971
|
config | configure) _dispatch_config "$@" ;;
|
|
1843
1972
|
uninstall | remove) cmd_uninstall ;;
|
|
1844
1973
|
version | v | -v | --version) cmd_version ;;
|
package/package.json
CHANGED
|
@@ -14,6 +14,11 @@ PULSE_STALE_THRESHOLD_SECONDS=1800
|
|
|
14
14
|
# future cadence shift only touches one place.
|
|
15
15
|
CRON_HOURLY="0 * * * *"
|
|
16
16
|
|
|
17
|
+
# Cron expression: every minute. Shared by process-guard, memory-pressure
|
|
18
|
+
# monitor, and pulse-watchdog schedulers (cron's minimum granularity).
|
|
19
|
+
# Kept DRY for the same reason as CRON_HOURLY.
|
|
20
|
+
CRON_EVERY_MINUTE="* * * * *"
|
|
21
|
+
|
|
17
22
|
# Resolve the modern bash binary path for use in launchd ProgramArguments.
|
|
18
23
|
# Launchd bypasses the shebang when ProgramArguments specifies an explicit
|
|
19
24
|
# interpreter, so we must resolve the path at plist generation time.
|
|
@@ -668,7 +673,12 @@ ${_env_overrides_xml} </dict>
|
|
|
668
673
|
<key>RunAtLoad</key>
|
|
669
674
|
<true/>
|
|
670
675
|
<key>KeepAlive</key>
|
|
671
|
-
<
|
|
676
|
+
<dict>
|
|
677
|
+
<key>SuccessfulExit</key>
|
|
678
|
+
<false/>
|
|
679
|
+
</dict>
|
|
680
|
+
<key>ThrottleInterval</key>
|
|
681
|
+
<integer>30</integer>
|
|
672
682
|
</dict>
|
|
673
683
|
</plist>
|
|
674
684
|
PLIST
|
|
@@ -703,6 +713,17 @@ _install_pulse_launchd() {
|
|
|
703
713
|
_interval_label="${_interval_sec}s"
|
|
704
714
|
fi
|
|
705
715
|
|
|
716
|
+
# One-time legacy cleanup: unload and remove the old-label plist if present.
|
|
717
|
+
# Users on stale installs may have com.aidevops.supervisor-pulse (legacy) and
|
|
718
|
+
# com.aidevops.aidevops-supervisor-pulse (current) both loaded, causing 2x
|
|
719
|
+
# dispatch. Only targets the hardcoded legacy path; idempotent — no-op when
|
|
720
|
+
# the legacy file is absent.
|
|
721
|
+
local _legacy_plist="$HOME/Library/LaunchAgents/com.aidevops.supervisor-pulse.plist"
|
|
722
|
+
if [[ -f "$_legacy_plist" ]]; then
|
|
723
|
+
launchctl unload "$_legacy_plist" 2>/dev/null || true
|
|
724
|
+
rm -f "$_legacy_plist"
|
|
725
|
+
fi
|
|
726
|
+
|
|
706
727
|
# _launchd_install_if_changed handles unload-before-replace only when content
|
|
707
728
|
# has changed, and writes atomically via tmp+rename (see setup.sh).
|
|
708
729
|
# shell-portability: ignore next — _install_pulse_launchd is macOS-only (launchd)
|
|
@@ -720,6 +741,161 @@ _install_pulse_launchd() {
|
|
|
720
741
|
return 0
|
|
721
742
|
}
|
|
722
743
|
|
|
744
|
+
# Generate the pulse-watchdog launchd plist XML content.
|
|
745
|
+
# Args: $1=label, $2=tick_script, $3=bash_bin
|
|
746
|
+
# Prints the complete plist XML to stdout.
|
|
747
|
+
#
|
|
748
|
+
# The watchdog is an independent launchd job that runs every 60s and revives
|
|
749
|
+
# pulse if it has been dead longer than (StartInterval + grace). Layered
|
|
750
|
+
# defense alongside the pulse plist's KeepAlive=<dict><SuccessfulExit=false>
|
|
751
|
+
# (auto-restart on crash) and StartInterval (scheduled cadence). Catches the
|
|
752
|
+
# "clean exit + lost launchd schedule" failure mode that no other layer covers.
|
|
753
|
+
# (t2939)
|
|
754
|
+
_generate_pulse_watchdog_plist_content() {
|
|
755
|
+
local watchdog_label="$1"
|
|
756
|
+
local tick_script="$2"
|
|
757
|
+
local bash_bin="$3"
|
|
758
|
+
|
|
759
|
+
local _xml_label _xml_tick _xml_bash _xml_home _xml_path
|
|
760
|
+
_xml_label=$(_xml_escape "$watchdog_label")
|
|
761
|
+
_xml_tick=$(_xml_escape "$tick_script")
|
|
762
|
+
_xml_bash=$(_xml_escape "$bash_bin")
|
|
763
|
+
_xml_home=$(_xml_escape "$HOME")
|
|
764
|
+
_xml_path=$(_xml_escape "$PATH")
|
|
765
|
+
|
|
766
|
+
cat <<PLIST
|
|
767
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
768
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
769
|
+
<plist version="1.0">
|
|
770
|
+
<dict>
|
|
771
|
+
<key>Label</key>
|
|
772
|
+
<string>${_xml_label}</string>
|
|
773
|
+
<key>ProgramArguments</key>
|
|
774
|
+
<array>
|
|
775
|
+
<string>${_xml_bash}</string>
|
|
776
|
+
<string>${_xml_tick}</string>
|
|
777
|
+
</array>
|
|
778
|
+
<key>StartInterval</key>
|
|
779
|
+
<integer>60</integer>
|
|
780
|
+
<key>StandardOutPath</key>
|
|
781
|
+
<string>${_xml_home}/.aidevops/logs/pulse-watchdog-launchd.log</string>
|
|
782
|
+
<key>StandardErrorPath</key>
|
|
783
|
+
<string>${_xml_home}/.aidevops/logs/pulse-watchdog-launchd.log</string>
|
|
784
|
+
<key>EnvironmentVariables</key>
|
|
785
|
+
<dict>
|
|
786
|
+
<key>PATH</key>
|
|
787
|
+
<string>${_xml_path}</string>
|
|
788
|
+
<key>HOME</key>
|
|
789
|
+
<string>${_xml_home}</string>
|
|
790
|
+
</dict>
|
|
791
|
+
<key>RunAtLoad</key>
|
|
792
|
+
<true/>
|
|
793
|
+
<key>KeepAlive</key>
|
|
794
|
+
<false/>
|
|
795
|
+
<key>ThrottleInterval</key>
|
|
796
|
+
<integer>30</integer>
|
|
797
|
+
</dict>
|
|
798
|
+
</plist>
|
|
799
|
+
PLIST
|
|
800
|
+
return 0
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
# Install the pulse-watchdog via launchd (macOS).
|
|
804
|
+
# t2939: independent revival mechanism — see _generate_pulse_watchdog_plist_content
|
|
805
|
+
# header for the layering rationale.
|
|
806
|
+
_install_pulse_watchdog_launchd() {
|
|
807
|
+
local watchdog_label="sh.aidevops.pulse-watchdog"
|
|
808
|
+
local tick_script="$HOME/.aidevops/agents/scripts/pulse-watchdog-tick.sh"
|
|
809
|
+
local watchdog_plist="$HOME/Library/LaunchAgents/${watchdog_label}.plist"
|
|
810
|
+
|
|
811
|
+
# Refuse to install if the tick script is missing — the watchdog would
|
|
812
|
+
# fire-and-fail every 60s, polluting logs without doing useful work.
|
|
813
|
+
if [[ ! -x "$tick_script" ]]; then
|
|
814
|
+
print_warning "Pulse watchdog tick script missing or non-executable: $tick_script"
|
|
815
|
+
return 1
|
|
816
|
+
fi
|
|
817
|
+
|
|
818
|
+
local _xml_bash_bin
|
|
819
|
+
_xml_bash_bin=$(_resolve_modern_bash)
|
|
820
|
+
|
|
821
|
+
local watchdog_plist_content
|
|
822
|
+
watchdog_plist_content=$(_generate_pulse_watchdog_plist_content "$watchdog_label" "$tick_script" "$_xml_bash_bin")
|
|
823
|
+
|
|
824
|
+
if [[ -z "$watchdog_plist_content" ]]; then
|
|
825
|
+
print_warning "Pulse watchdog plist generation produced empty content — skipping"
|
|
826
|
+
return 1
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
# shell-portability: ignore next — _install_pulse_watchdog_launchd is macOS-only
|
|
830
|
+
if _launchd_install_if_changed "$watchdog_label" "$watchdog_plist" "$watchdog_plist_content"; then
|
|
831
|
+
print_info "Pulse watchdog enabled (launchd, every 60s)"
|
|
832
|
+
else
|
|
833
|
+
print_warning "Failed to load pulse watchdog LaunchAgent"
|
|
834
|
+
fi
|
|
835
|
+
return 0
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
# Install the pulse-watchdog via systemd (Linux).
|
|
839
|
+
# t2939: parallels _install_pulse_watchdog_launchd for systems with systemd --user.
|
|
840
|
+
_install_pulse_watchdog_systemd() {
|
|
841
|
+
local tick_script="$HOME/.aidevops/agents/scripts/pulse-watchdog-tick.sh"
|
|
842
|
+
local watchdog_systemd="aidevops-pulse-watchdog"
|
|
843
|
+
local watchdog_log="$HOME/.aidevops/logs/pulse-watchdog-launchd.log"
|
|
844
|
+
|
|
845
|
+
if [[ ! -x "$tick_script" ]]; then
|
|
846
|
+
print_warning "Pulse watchdog tick script missing or non-executable: $tick_script"
|
|
847
|
+
return 1
|
|
848
|
+
fi
|
|
849
|
+
|
|
850
|
+
# Reuse the standard scheduler installer (cron-fallback aware).
|
|
851
|
+
# StartInterval=60 maps to every-minute cron schedule.
|
|
852
|
+
# shell-portability: ignore next — _install_scheduler_linux is Linux-only
|
|
853
|
+
_install_scheduler_linux \
|
|
854
|
+
"$watchdog_systemd" \
|
|
855
|
+
"aidevops: pulse-watchdog" \
|
|
856
|
+
"$CRON_EVERY_MINUTE" \
|
|
857
|
+
"\"${tick_script}\"" \
|
|
858
|
+
"60" \
|
|
859
|
+
"$watchdog_log" \
|
|
860
|
+
"" \
|
|
861
|
+
"Pulse watchdog enabled (every 60s)" \
|
|
862
|
+
"Failed to install pulse watchdog scheduler" \
|
|
863
|
+
"true" \
|
|
864
|
+
"false"
|
|
865
|
+
return 0
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
# Setup the pulse-watchdog scheduler (parallels setup_supervisor_pulse).
|
|
869
|
+
# t2939: layered defense — only installs when supervisor pulse is enabled,
|
|
870
|
+
# since a watchdog without a pulse to watch is a no-op every 60s.
|
|
871
|
+
#
|
|
872
|
+
# Args: $1 = pulse effective state ("true"/"false")
|
|
873
|
+
setup_pulse_watchdog() {
|
|
874
|
+
local _pulse_effective="$1"
|
|
875
|
+
local watchdog_label="sh.aidevops.pulse-watchdog"
|
|
876
|
+
local watchdog_systemd="aidevops-pulse-watchdog"
|
|
877
|
+
|
|
878
|
+
if [[ "$_pulse_effective" != "true" ]]; then
|
|
879
|
+
# Pulse disabled — uninstall the watchdog if present.
|
|
880
|
+
_uninstall_scheduler \
|
|
881
|
+
"$(uname -s)" \
|
|
882
|
+
"$watchdog_label" \
|
|
883
|
+
"$watchdog_systemd" \
|
|
884
|
+
"aidevops: pulse-watchdog" \
|
|
885
|
+
"Pulse watchdog disabled (pulse is off)"
|
|
886
|
+
return 0
|
|
887
|
+
fi
|
|
888
|
+
|
|
889
|
+
mkdir -p "$HOME/.aidevops/logs"
|
|
890
|
+
|
|
891
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
892
|
+
_install_pulse_watchdog_launchd
|
|
893
|
+
else
|
|
894
|
+
_install_pulse_watchdog_systemd
|
|
895
|
+
fi
|
|
896
|
+
return 0
|
|
897
|
+
}
|
|
898
|
+
|
|
723
899
|
# Check if systemd user services are available on this Linux system.
|
|
724
900
|
# Returns 0 if systemd --user is functional, 1 otherwise.
|
|
725
901
|
_systemd_user_available() {
|
|
@@ -1341,7 +1517,7 @@ GUARD_PLIST
|
|
|
1341
1517
|
_install_scheduler_linux \
|
|
1342
1518
|
"$guard_systemd" \
|
|
1343
1519
|
"aidevops: process-guard" \
|
|
1344
|
-
"
|
|
1520
|
+
"$CRON_EVERY_MINUTE" \
|
|
1345
1521
|
"\"${guard_script}\" kill-runaways" \
|
|
1346
1522
|
"30" \
|
|
1347
1523
|
"$guard_log" \
|
|
@@ -1433,7 +1609,7 @@ MONITOR_PLIST
|
|
|
1433
1609
|
_install_scheduler_linux \
|
|
1434
1610
|
"$monitor_systemd" \
|
|
1435
1611
|
"aidevops: memory-pressure-monitor" \
|
|
1436
|
-
"
|
|
1612
|
+
"$CRON_EVERY_MINUTE" \
|
|
1437
1613
|
"\"${monitor_script}\"" \
|
|
1438
1614
|
"60" \
|
|
1439
1615
|
"$monitor_log" \
|
|
@@ -1799,6 +1975,118 @@ setup_complexity_scan() {
|
|
|
1799
1975
|
return 0
|
|
1800
1976
|
}
|
|
1801
1977
|
|
|
1978
|
+
# Install pulse-merge-routine launchd plist (macOS).
|
|
1979
|
+
# Args: $1=label $2=script $3=log_dir
|
|
1980
|
+
_install_pulse_merge_routine_launchd() {
|
|
1981
|
+
local pmr_label="$1"
|
|
1982
|
+
local pmr_script="$2"
|
|
1983
|
+
local _pmr_log_dir="$3"
|
|
1984
|
+
local pmr_plist="$HOME/Library/LaunchAgents/${pmr_label}.plist"
|
|
1985
|
+
|
|
1986
|
+
local _xml_pmr_script _xml_pmr_home _xml_pmr_log_dir
|
|
1987
|
+
_xml_pmr_script=$(_xml_escape "$pmr_script")
|
|
1988
|
+
_xml_pmr_home=$(_xml_escape "$HOME")
|
|
1989
|
+
_xml_pmr_log_dir=$(_xml_escape "$_pmr_log_dir")
|
|
1990
|
+
|
|
1991
|
+
local pmr_plist_content
|
|
1992
|
+
pmr_plist_content=$(
|
|
1993
|
+
cat <<PMR_PLIST
|
|
1994
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1995
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1996
|
+
<plist version="1.0">
|
|
1997
|
+
<dict>
|
|
1998
|
+
<key>Label</key>
|
|
1999
|
+
<string>${pmr_label}</string>
|
|
2000
|
+
<key>ProgramArguments</key>
|
|
2001
|
+
<array>
|
|
2002
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
2003
|
+
<string>${_xml_pmr_script}</string>
|
|
2004
|
+
<string>run</string>
|
|
2005
|
+
</array>
|
|
2006
|
+
<key>StartInterval</key>
|
|
2007
|
+
<integer>120</integer>
|
|
2008
|
+
<key>StandardOutPath</key>
|
|
2009
|
+
<string>${_xml_pmr_log_dir}/pulse-merge-routine.log</string>
|
|
2010
|
+
<key>StandardErrorPath</key>
|
|
2011
|
+
<string>${_xml_pmr_log_dir}/pulse-merge-routine.log</string>
|
|
2012
|
+
<key>EnvironmentVariables</key>
|
|
2013
|
+
<dict>
|
|
2014
|
+
<key>PATH</key>
|
|
2015
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
2016
|
+
<key>HOME</key>
|
|
2017
|
+
<string>${_xml_pmr_home}</string>
|
|
2018
|
+
</dict>
|
|
2019
|
+
<key>RunAtLoad</key>
|
|
2020
|
+
<true/>
|
|
2021
|
+
<key>KeepAlive</key>
|
|
2022
|
+
<false/>
|
|
2023
|
+
<key>ProcessType</key>
|
|
2024
|
+
<string>Background</string>
|
|
2025
|
+
<key>LowPriorityBackgroundIO</key>
|
|
2026
|
+
<true/>
|
|
2027
|
+
<key>Nice</key>
|
|
2028
|
+
<integer>10</integer>
|
|
2029
|
+
</dict>
|
|
2030
|
+
</plist>
|
|
2031
|
+
PMR_PLIST
|
|
2032
|
+
)
|
|
2033
|
+
|
|
2034
|
+
if _launchd_install_if_changed "$pmr_label" "$pmr_plist" "$pmr_plist_content"; then
|
|
2035
|
+
print_info "Pulse merge routine enabled (launchd, every 2 min)"
|
|
2036
|
+
else
|
|
2037
|
+
print_warning "Failed to load pulse merge routine LaunchAgent"
|
|
2038
|
+
fi
|
|
2039
|
+
return 0
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
# Install pulse-merge-routine via systemd or cron (Linux).
|
|
2043
|
+
# Args: $1=script path, $2=log dir
|
|
2044
|
+
_install_pulse_merge_routine_linux() {
|
|
2045
|
+
local pmr_script="$1"
|
|
2046
|
+
local _pmr_log_dir="$2"
|
|
2047
|
+
local pmr_systemd="aidevops-pulse-merge-routine"
|
|
2048
|
+
_install_scheduler_linux \
|
|
2049
|
+
"$pmr_systemd" \
|
|
2050
|
+
"aidevops: pulse-merge-routine" \
|
|
2051
|
+
"*/2 * * * *" \
|
|
2052
|
+
"\"${pmr_script}\" run" \
|
|
2053
|
+
"120" \
|
|
2054
|
+
"${_pmr_log_dir}/pulse-merge-routine.log" \
|
|
2055
|
+
"" \
|
|
2056
|
+
"Pulse merge routine enabled (every 2 min)" \
|
|
2057
|
+
"Failed to install pulse merge routine scheduler" \
|
|
2058
|
+
"true" \
|
|
2059
|
+
"true"
|
|
2060
|
+
return 0
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
# Setup pulse merge routine (t2862, GH#20919) — runs merge_ready_prs_all_repos()
|
|
2064
|
+
# as a fast 120s standalone routine, decoupled from the monolithic pulse cycle.
|
|
2065
|
+
# The pulse cycle's preflight stack (60-470s) meant the merge pass ran only ~7
|
|
2066
|
+
# times/24h despite ~40+ cycles. This routine ensures green PRs merge within ~3
|
|
2067
|
+
# min of CI completion. The in-cycle merge call in pulse-wrapper.sh is kept as
|
|
2068
|
+
# defense-in-depth but short-circuits when this routine ran within the last 60s.
|
|
2069
|
+
setup_pulse_merge_routine() {
|
|
2070
|
+
local pmr_script="$HOME/.aidevops/agents/scripts/pulse-merge-routine.sh"
|
|
2071
|
+
local pmr_label="sh.aidevops.pulse-merge-routine"
|
|
2072
|
+
if ! [[ -x "$pmr_script" ]]; then
|
|
2073
|
+
return 0
|
|
2074
|
+
fi
|
|
2075
|
+
|
|
2076
|
+
# Reuse contribution-watch's log-dir resolver (same logic, same config key).
|
|
2077
|
+
local _pmr_log_dir
|
|
2078
|
+
_pmr_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
2079
|
+
mkdir -p "$_pmr_log_dir"
|
|
2080
|
+
|
|
2081
|
+
# Install/update scheduled runner
|
|
2082
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
2083
|
+
_install_pulse_merge_routine_launchd "$pmr_label" "$pmr_script" "$_pmr_log_dir"
|
|
2084
|
+
else
|
|
2085
|
+
_install_pulse_merge_routine_linux "$pmr_script" "$_pmr_log_dir"
|
|
2086
|
+
fi
|
|
2087
|
+
return 0
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1802
2090
|
# Setup draft responses — private repo + local draft storage for reviewing
|
|
1803
2091
|
# AI-drafted replies to external contributions (t1555).
|
|
1804
2092
|
# Respects config: aidevops config set orchestration.draft_responses false
|
|
@@ -2276,3 +2564,126 @@ setup_repo_aidevops_health() {
|
|
|
2276
2564
|
fi
|
|
2277
2565
|
return 0
|
|
2278
2566
|
}
|
|
2567
|
+
|
|
2568
|
+
# ============================================================================
|
|
2569
|
+
# Peer productivity monitor (t2932)
|
|
2570
|
+
# ============================================================================
|
|
2571
|
+
#
|
|
2572
|
+
# Adaptive cross-runner dispatch coordination: observes peer GitHub activity
|
|
2573
|
+
# every 30 min and updates ~/.config/aidevops/dispatch-override.conf to
|
|
2574
|
+
# `ignore` peers whose pulse is broken (claims issues but never PRs) and
|
|
2575
|
+
# back to `honour` when they recover. Self-healing across the ecosystem —
|
|
2576
|
+
# each runner observes peers independently, no central coordinator needed.
|
|
2577
|
+
# Manual entries in dispatch-override.conf above the auto-managed marker
|
|
2578
|
+
# always take precedence.
|
|
2579
|
+
|
|
2580
|
+
# Install peer-productivity-monitor launchd plist (macOS).
|
|
2581
|
+
# Args: $1=label $2=script $3=log_dir
|
|
2582
|
+
_install_peer_productivity_monitor_launchd() {
|
|
2583
|
+
local ppm_label="$1"
|
|
2584
|
+
local ppm_script="$2"
|
|
2585
|
+
local _ppm_log_dir="$3"
|
|
2586
|
+
local ppm_plist="$HOME/Library/LaunchAgents/${ppm_label}.plist"
|
|
2587
|
+
|
|
2588
|
+
local _xml_ppm_script _xml_ppm_home _xml_ppm_log_dir
|
|
2589
|
+
_xml_ppm_script=$(_xml_escape "$ppm_script")
|
|
2590
|
+
_xml_ppm_home=$(_xml_escape "$HOME")
|
|
2591
|
+
_xml_ppm_log_dir=$(_xml_escape "$_ppm_log_dir")
|
|
2592
|
+
|
|
2593
|
+
local ppm_plist_content
|
|
2594
|
+
ppm_plist_content=$(
|
|
2595
|
+
cat <<PPM_PLIST
|
|
2596
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2597
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
2598
|
+
<plist version="1.0">
|
|
2599
|
+
<dict>
|
|
2600
|
+
<key>Label</key>
|
|
2601
|
+
<string>${ppm_label}</string>
|
|
2602
|
+
<key>ProgramArguments</key>
|
|
2603
|
+
<array>
|
|
2604
|
+
<string>$(_xml_escape "$(_resolve_modern_bash)")</string>
|
|
2605
|
+
<string>${_xml_ppm_script}</string>
|
|
2606
|
+
<string>observe</string>
|
|
2607
|
+
</array>
|
|
2608
|
+
<key>StartInterval</key>
|
|
2609
|
+
<integer>1800</integer>
|
|
2610
|
+
<key>StandardOutPath</key>
|
|
2611
|
+
<string>${_xml_ppm_log_dir}/peer-productivity-launchd.log</string>
|
|
2612
|
+
<key>StandardErrorPath</key>
|
|
2613
|
+
<string>${_xml_ppm_log_dir}/peer-productivity-launchd.log</string>
|
|
2614
|
+
<key>EnvironmentVariables</key>
|
|
2615
|
+
<dict>
|
|
2616
|
+
<key>PATH</key>
|
|
2617
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
2618
|
+
<key>HOME</key>
|
|
2619
|
+
<string>${_xml_ppm_home}</string>
|
|
2620
|
+
</dict>
|
|
2621
|
+
<key>RunAtLoad</key>
|
|
2622
|
+
<true/>
|
|
2623
|
+
<key>KeepAlive</key>
|
|
2624
|
+
<false/>
|
|
2625
|
+
<key>ProcessType</key>
|
|
2626
|
+
<string>Background</string>
|
|
2627
|
+
<key>LowPriorityBackgroundIO</key>
|
|
2628
|
+
<true/>
|
|
2629
|
+
<key>Nice</key>
|
|
2630
|
+
<integer>10</integer>
|
|
2631
|
+
</dict>
|
|
2632
|
+
</plist>
|
|
2633
|
+
PPM_PLIST
|
|
2634
|
+
)
|
|
2635
|
+
|
|
2636
|
+
if _launchd_install_if_changed "$ppm_label" "$ppm_plist" "$ppm_plist_content"; then
|
|
2637
|
+
print_info "Peer productivity monitor enabled (launchd, every 30 min)"
|
|
2638
|
+
else
|
|
2639
|
+
print_warning "Failed to load peer-productivity-monitor LaunchAgent"
|
|
2640
|
+
fi
|
|
2641
|
+
return 0
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
# Install peer-productivity-monitor via systemd or cron (Linux).
|
|
2645
|
+
# Args: $1=script path, $2=log dir
|
|
2646
|
+
_install_peer_productivity_monitor_linux() {
|
|
2647
|
+
local ppm_script="$1"
|
|
2648
|
+
local _ppm_log_dir="$2"
|
|
2649
|
+
local ppm_systemd="aidevops-peer-productivity-monitor"
|
|
2650
|
+
_install_scheduler_linux \
|
|
2651
|
+
"$ppm_systemd" \
|
|
2652
|
+
"aidevops: peer-productivity-monitor" \
|
|
2653
|
+
"*/30 * * * *" \
|
|
2654
|
+
"\"${ppm_script}\" observe" \
|
|
2655
|
+
"1800" \
|
|
2656
|
+
"${_ppm_log_dir}/peer-productivity-launchd.log" \
|
|
2657
|
+
"" \
|
|
2658
|
+
"Peer productivity monitor enabled (every 30 min)" \
|
|
2659
|
+
"Failed to install peer-productivity-monitor scheduler" \
|
|
2660
|
+
"true" \
|
|
2661
|
+
"true"
|
|
2662
|
+
return 0
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
# Setup peer-productivity-monitor (t2932) — observes peer GitHub activity
|
|
2666
|
+
# every 30 min and updates ~/.config/aidevops/dispatch-override.conf so the
|
|
2667
|
+
# local pulse competes with broken peers and collaborates with healthy ones.
|
|
2668
|
+
# Manual entries in dispatch-override.conf above the auto-managed marker
|
|
2669
|
+
# always take precedence.
|
|
2670
|
+
setup_peer_productivity_monitor() {
|
|
2671
|
+
local ppm_script="$HOME/.aidevops/agents/scripts/peer-productivity-monitor.sh"
|
|
2672
|
+
local ppm_label="sh.aidevops.peer-productivity-monitor"
|
|
2673
|
+
if ! [[ -x "$ppm_script" ]]; then
|
|
2674
|
+
return 0
|
|
2675
|
+
fi
|
|
2676
|
+
|
|
2677
|
+
# Reuse contribution-watch's log-dir resolver (same logic, same config key).
|
|
2678
|
+
local _ppm_log_dir
|
|
2679
|
+
_ppm_log_dir=$(_resolve_cw_log_dir) || return 1
|
|
2680
|
+
mkdir -p "$_ppm_log_dir"
|
|
2681
|
+
|
|
2682
|
+
# Install/update scheduled runner
|
|
2683
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
2684
|
+
_install_peer_productivity_monitor_launchd "$ppm_label" "$ppm_script" "$_ppm_log_dir"
|
|
2685
|
+
else
|
|
2686
|
+
_install_peer_productivity_monitor_linux "$ppm_script" "$_ppm_log_dir"
|
|
2687
|
+
fi
|
|
2688
|
+
return 0
|
|
2689
|
+
}
|
|
@@ -390,12 +390,14 @@ setup_shell_linting_tools() {
|
|
|
390
390
|
|
|
391
391
|
setup_setsid_advisory() {
|
|
392
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
|
|
394
|
-
# killed by any PG-scoped signal (launchd unload, restart chain).
|
|
393
|
+
# (t2757, GH#20561, GH#21102). Without it, workers inherit pulse's PGID and
|
|
394
|
+
# are killed by any PG-scoped signal (launchd unload, restart chain).
|
|
395
395
|
#
|
|
396
396
|
# Linux: setsid ships with util-linux (present on all mainstream distros).
|
|
397
|
-
# macOS: available from macOS 12+ at /usr/bin/setsid. Older
|
|
398
|
-
#
|
|
397
|
+
# macOS: available from macOS 12+ at /usr/bin/setsid. Older macOS or systems
|
|
398
|
+
# where /usr/bin/setsid is absent need util-linux via Homebrew.
|
|
399
|
+
# util-linux is keg-only on Homebrew — binary is not linked into PATH
|
|
400
|
+
# automatically, so we create a symlink after install.
|
|
399
401
|
if command -v setsid >/dev/null 2>&1; then
|
|
400
402
|
local setsid_path
|
|
401
403
|
setsid_path="$(command -v setsid)"
|
|
@@ -403,23 +405,51 @@ setup_setsid_advisory() {
|
|
|
403
405
|
return 0
|
|
404
406
|
fi
|
|
405
407
|
|
|
406
|
-
# setsid missing —
|
|
407
|
-
#
|
|
408
|
-
|
|
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 ""
|
|
408
|
+
# setsid missing — on macOS with Homebrew, auto-install util-linux and
|
|
409
|
+
# symlink setsid into PATH (GH#21102 / t2926). On Linux and macOS without
|
|
410
|
+
# Homebrew, emit an actionable error with install instructions.
|
|
412
411
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
413
412
|
if command -v brew >/dev/null 2>&1; then
|
|
414
|
-
|
|
413
|
+
print_info "setsid not found — installing util-linux for worker PGID isolation (GH#21102)"
|
|
414
|
+
if brew install util-linux 2>&1 | tail -3; then
|
|
415
|
+
# util-linux is keg-only: binary lives under the keg, not in /opt/homebrew/bin.
|
|
416
|
+
# Symlink setsid into a standard PATH directory so 'command -v setsid' works.
|
|
417
|
+
local brew_prefix
|
|
418
|
+
brew_prefix="$(brew --prefix 2>/dev/null || echo "")"
|
|
419
|
+
local keg_setsid="${brew_prefix}/opt/util-linux/bin/setsid"
|
|
420
|
+
local link_target="${brew_prefix}/bin/setsid"
|
|
421
|
+
if [[ -x "$keg_setsid" && ! -e "$link_target" ]]; then
|
|
422
|
+
ln -s "$keg_setsid" "$link_target" && \
|
|
423
|
+
print_success "Symlinked setsid: $keg_setsid → $link_target"
|
|
424
|
+
fi
|
|
425
|
+
# Verify setsid is now reachable
|
|
426
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
427
|
+
print_success "setsid installed at $(command -v setsid) (worker PGID isolation enabled)"
|
|
428
|
+
else
|
|
429
|
+
print_error "util-linux installed but setsid still not in PATH — check brew --prefix"
|
|
430
|
+
fi
|
|
431
|
+
else
|
|
432
|
+
print_error "brew install util-linux failed — workers will share pulse PGID until resolved"
|
|
433
|
+
echo " Manual fix: brew install util-linux"
|
|
434
|
+
fi
|
|
415
435
|
else
|
|
416
|
-
|
|
436
|
+
print_error "setsid not found — worker isolation broken; install util-linux"
|
|
437
|
+
echo " Impact: every pulse restart sends SIGHUP to workers in its PGID,"
|
|
438
|
+
echo " killing in-flight workers before they can finish (GH#21102)"
|
|
439
|
+
echo " Fix: install Homebrew, then run: brew install util-linux"
|
|
417
440
|
echo " Or upgrade to macOS 12+ where /usr/bin/setsid ships by default"
|
|
418
441
|
fi
|
|
419
442
|
else
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
443
|
+
# Linux: setsid should be present on all mainstream distros via util-linux.
|
|
444
|
+
# If it is missing, emit an error rather than a warning — workers will be
|
|
445
|
+
# killed on every pulse cycle restart without it.
|
|
446
|
+
print_error "setsid not found — worker isolation broken; install util-linux"
|
|
447
|
+
echo " Impact: every pulse restart sends SIGHUP to workers in its PGID,"
|
|
448
|
+
echo " killing in-flight workers before they can finish (GH#21102)"
|
|
449
|
+
echo " Fix: sudo apt install util-linux # Debian/Ubuntu"
|
|
450
|
+
echo " sudo dnf install util-linux # Fedora/RHEL"
|
|
451
|
+
echo " sudo pacman -S util-linux # Arch"
|
|
452
|
+
echo " sudo apk add util-linux # Alpine"
|
|
423
453
|
fi
|
|
424
454
|
echo ""
|
|
425
455
|
|
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.
|
|
15
|
+
# Version: 3.13.0
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|
|
@@ -243,7 +243,16 @@ _launchd_install_if_changed() {
|
|
|
243
243
|
|
|
244
244
|
# Atomic write: build at sibling tmp path, then rename into place.
|
|
245
245
|
# If printf is killed mid-write, the destination is untouched.
|
|
246
|
-
|
|
246
|
+
# mktemp avoids predictable tmp names (defense-in-depth against symlink attacks).
|
|
247
|
+
local tmp_plist
|
|
248
|
+
tmp_plist=$(mktemp "${plist_path}.XXXXXX") || return 1
|
|
249
|
+
# Guard: refuse to write empty content — catching this before the write avoids
|
|
250
|
+
# creating a tmp file that the file-size check would also catch, but the
|
|
251
|
+
# content check is more direct and gives a clearer failure point.
|
|
252
|
+
if [[ -z "$new_content" ]]; then
|
|
253
|
+
rm -f "$tmp_plist"
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
247
256
|
if ! printf '%s\n' "$new_content" >"$tmp_plist"; then
|
|
248
257
|
rm -f "$tmp_plist"
|
|
249
258
|
return 1
|
|
@@ -254,7 +263,10 @@ _launchd_install_if_changed() {
|
|
|
254
263
|
rm -f "$tmp_plist"
|
|
255
264
|
return 1
|
|
256
265
|
fi
|
|
257
|
-
mv -f "$tmp_plist" "$plist_path"
|
|
266
|
+
if ! mv -f "$tmp_plist" "$plist_path"; then
|
|
267
|
+
rm -f "$tmp_plist"
|
|
268
|
+
return 1
|
|
269
|
+
fi
|
|
258
270
|
launchctl load "$plist_path" 2>/dev/null || return 1
|
|
259
271
|
return 0
|
|
260
272
|
}
|
|
@@ -959,6 +971,62 @@ _deploy_hotfix_config() {
|
|
|
959
971
|
return 0
|
|
960
972
|
}
|
|
961
973
|
|
|
974
|
+
# t2919: Early pulse plist install. The pulse launchd agent is critical
|
|
975
|
+
# infrastructure — without it, every other pulse-driven feature (worker
|
|
976
|
+
# dispatch, issue routing, cross-repo coordination) is dead. Previously,
|
|
977
|
+
# setup_supervisor_pulse only ran inside _setup_post_setup_steps which
|
|
978
|
+
# executes AFTER ~25 other migration/setup steps. When `aidevops update`
|
|
979
|
+
# runs unattended and any earlier step times out (e.g. brew taps, MCP
|
|
980
|
+
# installs, slow repo scans), the pulse plist never gets installed/refreshed
|
|
981
|
+
# and the runner falls behind.
|
|
982
|
+
#
|
|
983
|
+
# Install immediately after deploy_aidevops_agents (so the scripts the plist
|
|
984
|
+
# references already exist on disk). The late install in _setup_post_setup_steps
|
|
985
|
+
# remains as the canonical regenerate-on-change path — _launchd_install_if_changed
|
|
986
|
+
# compares content and skips reload when identical, so the second call is a
|
|
987
|
+
# no-op when nothing changed. Failure here is non-fatal: the late path retries.
|
|
988
|
+
_setup_install_pulse_plist_early() {
|
|
989
|
+
local _early_os
|
|
990
|
+
_early_os="$(uname -s)"
|
|
991
|
+
if _should_setup_noninteractive_supervisor_pulse; then
|
|
992
|
+
setup_supervisor_pulse "$_early_os" || print_warning "Early pulse plist install failed (will retry late)"
|
|
993
|
+
fi
|
|
994
|
+
return 0
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
# Provision knowledge planes for all repos in repos.json where knowledge != "off".
|
|
998
|
+
# Idempotent: already-provisioned directories are not modified.
|
|
999
|
+
# Called from the non-interactive setup path (update) and after interactive init.
|
|
1000
|
+
setup_knowledge_planes() {
|
|
1001
|
+
local repos_file="$HOME/.config/aidevops/repos.json"
|
|
1002
|
+
local helper
|
|
1003
|
+
helper="${BASH_SOURCE[0]%/*}/.agents/scripts/knowledge-helper.sh"
|
|
1004
|
+
if [[ ! -f "$helper" ]]; then
|
|
1005
|
+
helper="$HOME/.aidevops/agents/scripts/knowledge-helper.sh"
|
|
1006
|
+
fi
|
|
1007
|
+
if [[ ! -f "$helper" ]]; then
|
|
1008
|
+
print_warning "knowledge-helper.sh not found — skipping knowledge plane provisioning"
|
|
1009
|
+
return 0
|
|
1010
|
+
fi
|
|
1011
|
+
if [[ ! -f "$repos_file" ]]; then
|
|
1012
|
+
return 0
|
|
1013
|
+
fi
|
|
1014
|
+
if ! command -v jq &>/dev/null; then
|
|
1015
|
+
print_warning "jq not installed — skipping knowledge plane provisioning"
|
|
1016
|
+
return 0
|
|
1017
|
+
fi
|
|
1018
|
+
local repo_path mode
|
|
1019
|
+
while IFS=$'\t' read -r repo_path mode; do
|
|
1020
|
+
[[ -z "$repo_path" || "$mode" == "off" ]] && continue
|
|
1021
|
+
if [[ ! -d "$repo_path" ]]; then
|
|
1022
|
+
print_warning "knowledge-plane: repo path not found: $repo_path"
|
|
1023
|
+
continue
|
|
1024
|
+
fi
|
|
1025
|
+
bash "$helper" provision "$repo_path" || print_warning "knowledge-plane: provision failed for $repo_path"
|
|
1026
|
+
done < <(jq -r '.initialized_repos[] | select(.knowledge != null and .knowledge != "off") | [.path, .knowledge] | @tsv' "$repos_file" 2>/dev/null || true)
|
|
1027
|
+
return 0
|
|
1028
|
+
}
|
|
1029
|
+
|
|
962
1030
|
# Non-interactive path: deploy agents and run safe migrations only (no prompts).
|
|
963
1031
|
_setup_run_non_interactive() {
|
|
964
1032
|
print_info "Non-interactive mode: deploying agents and running safe migrations only"
|
|
@@ -966,6 +1034,9 @@ _setup_run_non_interactive() {
|
|
|
966
1034
|
check_requirements
|
|
967
1035
|
# Run quality tool detection in non-interactive mode too (warn-only path).
|
|
968
1036
|
check_quality_tools
|
|
1037
|
+
# Check setsid availability; auto-install util-linux on macOS if missing
|
|
1038
|
+
# (GH#21102 / t2926: missing setsid kills workers on every pulse restart).
|
|
1039
|
+
setup_setsid_advisory
|
|
969
1040
|
check_python_upgrade_available
|
|
970
1041
|
set_permissions
|
|
971
1042
|
migrate_old_backups
|
|
@@ -990,6 +1061,7 @@ _setup_run_non_interactive() {
|
|
|
990
1061
|
setup_opencode_cli
|
|
991
1062
|
validate_opencode_config
|
|
992
1063
|
deploy_aidevops_agents
|
|
1064
|
+
_setup_install_pulse_plist_early
|
|
993
1065
|
_deploy_hotfix_config
|
|
994
1066
|
sync_agent_sources
|
|
995
1067
|
install_aidevops_cli
|
|
@@ -1052,6 +1124,8 @@ _setup_run_non_interactive() {
|
|
|
1052
1124
|
# copies doesn't burn CPU (t2885). Idempotent. macOS only — Linux
|
|
1053
1125
|
# indexers tracked separately.
|
|
1054
1126
|
setup_worktree_exclusions
|
|
1127
|
+
# Provision knowledge planes for repos where knowledge != "off" (idempotent).
|
|
1128
|
+
setup_knowledge_planes
|
|
1055
1129
|
return 0
|
|
1056
1130
|
}
|
|
1057
1131
|
|
|
@@ -1172,6 +1246,12 @@ _setup_noninteractive_schedulers() {
|
|
|
1172
1246
|
if _should_setup_noninteractive_supervisor_pulse; then
|
|
1173
1247
|
setup_supervisor_pulse "$os"
|
|
1174
1248
|
fi
|
|
1249
|
+
# t2939: pulse-watchdog (independent revival mechanism). Always installed
|
|
1250
|
+
# alongside the pulse — it is a no-op when pulse is disabled. Skipping the
|
|
1251
|
+
# `_should_setup_noninteractive_*` guard intentionally: this is layered
|
|
1252
|
+
# defense, the cost of installing it is one plist file, and the user opts
|
|
1253
|
+
# in by enabling the pulse itself.
|
|
1254
|
+
setup_pulse_watchdog "${PULSE_ENABLED:-}"
|
|
1175
1255
|
# Regenerate other schedulers if already installed (GH#17695 Finding B).
|
|
1176
1256
|
# Stats wrapper is a pulse dependency — also install on first run when
|
|
1177
1257
|
# the supervisor pulse is consented (t2418, GH#20016).
|
|
@@ -1197,6 +1277,15 @@ _setup_noninteractive_schedulers() {
|
|
|
1197
1277
|
if _should_setup_noninteractive_scheduler "Complexity scan" "sh.aidevops.complexity-scan" "aidevops: complexity-scan" "aidevops-complexity-scan"; then
|
|
1198
1278
|
setup_complexity_scan
|
|
1199
1279
|
fi
|
|
1280
|
+
# t2862 (GH#20919): pulse merge routine — fast 120s standalone merge pass
|
|
1281
|
+
if _should_setup_noninteractive_scheduler "Pulse merge routine" "sh.aidevops.pulse-merge-routine" "aidevops: pulse-merge-routine" "aidevops-pulse-merge-routine"; then
|
|
1282
|
+
setup_pulse_merge_routine
|
|
1283
|
+
fi
|
|
1284
|
+
# t2932 (GH#21125): peer productivity monitor — adaptive cross-runner
|
|
1285
|
+
# dispatch coordination, runs every 30 min.
|
|
1286
|
+
if _should_setup_noninteractive_scheduler "Peer productivity monitor" "sh.aidevops.peer-productivity-monitor" "aidevops: peer-productivity-monitor" "aidevops-peer-productivity-monitor"; then
|
|
1287
|
+
setup_peer_productivity_monitor
|
|
1288
|
+
fi
|
|
1200
1289
|
# Repo sync handles non-interactive mode internally (systemd detection fixed in GH#17861)
|
|
1201
1290
|
setup_repo_sync
|
|
1202
1291
|
# r914 repo-aidevops-health — daily drift keeper (t2366)
|
|
@@ -1248,6 +1337,8 @@ _setup_post_setup_steps() {
|
|
|
1248
1337
|
# Post-setup: auto-update, schedulers, final instructions (GH#5793)
|
|
1249
1338
|
setup_auto_update
|
|
1250
1339
|
setup_supervisor_pulse "$os"
|
|
1340
|
+
# t2939: pulse-watchdog — independent revival mechanism, layered defense.
|
|
1341
|
+
setup_pulse_watchdog "${PULSE_ENABLED:-}"
|
|
1251
1342
|
setup_stats_wrapper "${PULSE_ENABLED:-}"
|
|
1252
1343
|
setup_failure_miner "${PULSE_ENABLED:-}"
|
|
1253
1344
|
setup_repo_sync
|
|
@@ -1258,6 +1349,7 @@ _setup_post_setup_steps() {
|
|
|
1258
1349
|
setup_screen_time_snapshot
|
|
1259
1350
|
setup_contribution_watch
|
|
1260
1351
|
setup_complexity_scan
|
|
1352
|
+
setup_pulse_merge_routine
|
|
1261
1353
|
setup_draft_responses
|
|
1262
1354
|
setup_profile_readme
|
|
1263
1355
|
setup_oauth_token_refresh
|